Change initial kill to SIGINT
This sends a ctrl+c signal to each process which will allow code to exit properly by handling KeyboardInterrupt Other notable changes: - create_daemon method used to clean up create daemon threads - create_echo_function used to reduce code duplication with messagebus echo functions - wait_for_exit_signal used to wait for ctrl+c (SIGINT) - reset_sigint_handler used to ensure SIGINT will raise KeyboardInterruptpull/1519/head
parent
70b9b320ed
commit
10bd9a1cf3
|
@ -18,17 +18,18 @@
|
||||||
This handles playback of audio and speech
|
This handles playback of audio and speech
|
||||||
"""
|
"""
|
||||||
import imp
|
import imp
|
||||||
import json
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from os import listdir
|
from os import listdir
|
||||||
|
|
||||||
from os.path import abspath, dirname, basename, isdir, join
|
from os.path import abspath, dirname, basename, isdir, join
|
||||||
|
|
||||||
import mycroft.audio.speech as speech
|
import mycroft.audio.speech as speech
|
||||||
from mycroft.configuration import Configuration
|
from mycroft.configuration import Configuration
|
||||||
from mycroft.messagebus.client.ws import WebsocketClient
|
from mycroft.messagebus.client.ws import WebsocketClient
|
||||||
from mycroft.messagebus.message import Message
|
from mycroft.messagebus.message import Message
|
||||||
|
from mycroft.util import reset_sigint_handler, wait_for_exit_signal, \
|
||||||
|
create_daemon, create_echo_function
|
||||||
from mycroft.util.log import LOG
|
from mycroft.util.log import LOG
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -36,7 +37,6 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pulsectl = None
|
pulsectl = None
|
||||||
|
|
||||||
|
|
||||||
MAINMODULE = '__init__'
|
MAINMODULE = '__init__'
|
||||||
sys.path.append(abspath(dirname(__file__)))
|
sys.path.append(abspath(dirname(__file__)))
|
||||||
|
|
||||||
|
@ -139,6 +139,7 @@ class AudioService(object):
|
||||||
Handles playback of audio and selecting proper backend for the uri
|
Handles playback of audio and selecting proper backend for the uri
|
||||||
to be played.
|
to be played.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ws):
|
def __init__(self, ws):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
|
@ -458,31 +459,20 @@ class AudioService(object):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
""" Main function. Run when file is invoked. """
|
""" Main function. Run when file is invoked. """
|
||||||
|
reset_sigint_handler()
|
||||||
ws = WebsocketClient()
|
ws = WebsocketClient()
|
||||||
Configuration.init(ws)
|
Configuration.init(ws)
|
||||||
speech.init(ws)
|
speech.init(ws)
|
||||||
|
|
||||||
def echo(message):
|
LOG.info("Starting Audio Services")
|
||||||
""" Echo message bus messages. """
|
ws.on('message', create_echo_function('AUDIO', ['mycroft.audio.service']))
|
||||||
try:
|
|
||||||
_message = json.loads(message)
|
|
||||||
if 'mycroft.audio.service' not in _message.get('type'):
|
|
||||||
return
|
|
||||||
message = json.dumps(_message)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(e)
|
|
||||||
LOG.debug(message)
|
|
||||||
|
|
||||||
LOG.info("Staring Audio Services")
|
|
||||||
ws.on('message', echo)
|
|
||||||
audio = AudioService(ws) # Connect audio service instance to message bus
|
audio = AudioService(ws) # Connect audio service instance to message bus
|
||||||
try:
|
create_daemon(ws.run_forever)
|
||||||
ws.run_forever()
|
|
||||||
except KeyboardInterrupt as e:
|
wait_for_exit_signal()
|
||||||
LOG.exception(e)
|
|
||||||
speech.shutdown()
|
speech.shutdown()
|
||||||
audio.shutdown()
|
audio.shutdown()
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -151,7 +151,6 @@ def init(websocket):
|
||||||
|
|
||||||
|
|
||||||
def shutdown():
|
def shutdown():
|
||||||
global tts
|
|
||||||
if tts:
|
if tts:
|
||||||
tts.playback.stop()
|
tts.playback.stop()
|
||||||
tts.playback.join()
|
tts.playback.join()
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import sys
|
from threading import Lock
|
||||||
from threading import Thread, Lock
|
|
||||||
|
|
||||||
from mycroft import dialog
|
from mycroft import dialog
|
||||||
from mycroft.client.enclosure.api import EnclosureAPI
|
from mycroft.client.enclosure.api import EnclosureAPI
|
||||||
|
@ -23,14 +22,14 @@ from mycroft.identity import IdentityManager
|
||||||
from mycroft.lock import Lock as PIDLock # Create/Support PID locking file
|
from mycroft.lock import Lock as PIDLock # Create/Support PID locking file
|
||||||
from mycroft.messagebus.client.ws import WebsocketClient
|
from mycroft.messagebus.client.ws import WebsocketClient
|
||||||
from mycroft.messagebus.message import Message
|
from mycroft.messagebus.message import Message
|
||||||
|
from mycroft.util import create_daemon, wait_for_exit_signal, \
|
||||||
|
reset_sigint_handler
|
||||||
from mycroft.util.log import LOG
|
from mycroft.util.log import LOG
|
||||||
|
|
||||||
ws = None
|
ws = None
|
||||||
lock = Lock()
|
lock = Lock()
|
||||||
loop = None
|
loop = None
|
||||||
|
|
||||||
config = Configuration.get()
|
|
||||||
|
|
||||||
|
|
||||||
def handle_record_begin():
|
def handle_record_begin():
|
||||||
LOG.info("Begin Recording...")
|
LOG.info("Begin Recording...")
|
||||||
|
@ -132,17 +131,12 @@ def handle_open():
|
||||||
EnclosureAPI(ws).reset()
|
EnclosureAPI(ws).reset()
|
||||||
|
|
||||||
|
|
||||||
def connect():
|
|
||||||
ws.run_forever()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global ws
|
global ws
|
||||||
global loop
|
global loop
|
||||||
global config
|
reset_sigint_handler()
|
||||||
lock = PIDLock("voice")
|
PIDLock("voice")
|
||||||
ws = WebsocketClient()
|
ws = WebsocketClient()
|
||||||
config = Configuration.get()
|
|
||||||
Configuration.init(ws)
|
Configuration.init(ws)
|
||||||
loop = RecognizerLoop()
|
loop = RecognizerLoop()
|
||||||
loop.on('recognizer_loop:utterance', handle_utterance)
|
loop.on('recognizer_loop:utterance', handle_utterance)
|
||||||
|
@ -163,15 +157,11 @@ def main():
|
||||||
ws.on('recognizer_loop:audio_output_start', handle_audio_start)
|
ws.on('recognizer_loop:audio_output_start', handle_audio_start)
|
||||||
ws.on('recognizer_loop:audio_output_end', handle_audio_end)
|
ws.on('recognizer_loop:audio_output_end', handle_audio_end)
|
||||||
ws.on('mycroft.stop', handle_stop)
|
ws.on('mycroft.stop', handle_stop)
|
||||||
event_thread = Thread(target=connect)
|
|
||||||
event_thread.setDaemon(True)
|
|
||||||
event_thread.start()
|
|
||||||
|
|
||||||
try:
|
create_daemon(ws.run_forever)
|
||||||
loop.run()
|
create_daemon(loop.run)
|
||||||
except KeyboardInterrupt as e:
|
|
||||||
LOG.exception(e)
|
wait_for_exit_signal()
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
from signal import getsignal, signal, SIGKILL, SIGINT, SIGTERM # signals
|
from signal import getsignal, signal, SIGKILL, SIGINT, SIGTERM, \
|
||||||
|
SIG_DFL, default_int_handler, SIG_IGN # signals
|
||||||
|
|
||||||
import os # Operating System functions
|
import os # Operating System functions
|
||||||
|
|
||||||
|
@ -20,16 +21,19 @@ import os # Operating System functions
|
||||||
#
|
#
|
||||||
# Wrapper around chain of handler functions for a specific system level signal.
|
# Wrapper around chain of handler functions for a specific system level signal.
|
||||||
# Often used to trap Ctrl-C for specific application purposes.
|
# Often used to trap Ctrl-C for specific application purposes.
|
||||||
|
from mycroft.util import LOG
|
||||||
|
|
||||||
|
|
||||||
class Signal(object): # python 3+ class Signal
|
class Signal(object): # python 3+ class Signal
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Capture and replace a signal handler with a user supplied function.
|
Capture and replace a signal handler with a user supplied function.
|
||||||
The user supplied function is always called first then the previous
|
The user supplied function is always called first then the previous
|
||||||
handler, if it exists, will be called. It is possible to chain several
|
handler, if it exists, will be called. It is possible to chain several
|
||||||
signal handlers together by creating multiply instances of objects of
|
signal handlers together by creating multiply instances of objects of
|
||||||
this class, providing a different user functions for each instance. All
|
this class, providing a different user functions for each instance. All
|
||||||
provided user functions will be called in LIFO order.
|
provided user functions will be called in LIFO order.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
#
|
#
|
||||||
# Constructor
|
# Constructor
|
||||||
|
@ -37,38 +41,40 @@ class Signal(object): # python 3+ class Signal
|
||||||
# as the new handler function for this signal
|
# as the new handler function for this signal
|
||||||
|
|
||||||
def __init__(self, sig_value, func):
|
def __init__(self, sig_value, func):
|
||||||
'''
|
"""
|
||||||
Create an instance of the signal handler class.
|
Create an instance of the signal handler class.
|
||||||
|
|
||||||
sig_value: The ID value of the signal to be captured.
|
sig_value: The ID value of the signal to be captured.
|
||||||
func: User supplied function that will act as the new signal handler.
|
func: User supplied function that will act as the new signal handler.
|
||||||
'''
|
"""
|
||||||
super(Signal, self).__init__() # python 3+ 'super().__init__()
|
super(Signal, self).__init__() # python 3+ 'super().__init__()
|
||||||
self.__sig_value = sig_value
|
self.__sig_value = sig_value
|
||||||
self.__user_func = func # store user passed function
|
self.__user_func = func # store user passed function
|
||||||
self.__previous_func = getsignal(sig_value) # get current handler
|
self.__previous_func = signal(sig_value, self)
|
||||||
signal(sig_value, self)
|
self.__previous_func = { # Convert signal codes to functions
|
||||||
|
SIG_DFL: default_int_handler,
|
||||||
|
SIG_IGN: lambda a, b: None
|
||||||
|
}.get(self.__previous_func, self.__previous_func)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Called to handle the passed signal
|
# Called to handle the passed signal
|
||||||
def __call__(self, signame, sf):
|
def __call__(self, signame, sf):
|
||||||
'''
|
"""
|
||||||
Allows the instance of this class to be called as a function.
|
Allows the instance of this class to be called as a function.
|
||||||
When called it runs the user supplied signal handler than
|
When called it runs the user supplied signal handler than
|
||||||
checks to see if there is a previously defined handler. If
|
checks to see if there is a previously defined handler. If
|
||||||
there is a previously defined handler call it.
|
there is a previously defined handler call it.
|
||||||
'''
|
"""
|
||||||
self.__user_func() # call user function
|
self.__user_func()
|
||||||
if self.__previous_func:
|
self.__previous_func(signame, sf)
|
||||||
self.__previous_func(signame, sf)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# reset the signal handler
|
# reset the signal handler
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
'''
|
"""
|
||||||
Class destructor. Called during garbage collection.
|
Class destructor. Called during garbage collection.
|
||||||
Resets the signal handler to the previous function.
|
Resets the signal handler to the previous function.
|
||||||
'''
|
"""
|
||||||
signal(self.__sig_value, self.__previous_func)
|
signal(self.__sig_value, self.__previous_func)
|
||||||
|
|
||||||
# End class Signal
|
# End class Signal
|
||||||
|
@ -83,12 +89,12 @@ class Signal(object): # python 3+ class Signal
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class Lock(object): # python 3+ 'class Lock'
|
class Lock(object): # python 3+ 'class Lock'
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Create and maintains the PID lock file for this application process.
|
Create and maintains the PID lock file for this application process.
|
||||||
The PID lock file is located in /tmp/mycroft/*.pid. If another process
|
The PID lock file is located in /tmp/mycroft/*.pid. If another process
|
||||||
of the same type is started, this class will 'attempt' to stop the
|
of the same type is started, this class will 'attempt' to stop the
|
||||||
previously running process and then change the process ID in the lock file.
|
previously running process and then change the process ID in the lock file.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
#
|
#
|
||||||
# Class constants
|
# Class constants
|
||||||
|
@ -98,13 +104,13 @@ class Lock(object): # python 3+ 'class Lock'
|
||||||
#
|
#
|
||||||
# Constructor
|
# Constructor
|
||||||
def __init__(self, service):
|
def __init__(self, service):
|
||||||
'''
|
"""
|
||||||
Builds the instance of this object. Holds the lock until the
|
Builds the instance of this object. Holds the lock until the
|
||||||
object is garbage collected.
|
object is garbage collected.
|
||||||
|
|
||||||
service: Text string. The name of the service application
|
service: Text string. The name of the service application
|
||||||
to be locked (ie: skills, voice)
|
to be locked (ie: skills, voice)
|
||||||
'''
|
"""
|
||||||
super(Lock, self).__init__() # python 3+ 'super().__init__()'
|
super(Lock, self).__init__() # python 3+ 'super().__init__()'
|
||||||
self.__pid = os.getpid() # PID of this application
|
self.__pid = os.getpid() # PID of this application
|
||||||
self.path = Lock.DIRECTORY + Lock.FILE.format(service)
|
self.path = Lock.DIRECTORY + Lock.FILE.format(service)
|
||||||
|
@ -114,11 +120,11 @@ class Lock(object): # python 3+ 'class Lock'
|
||||||
#
|
#
|
||||||
# Reset the signal handlers to the 'delete' function
|
# Reset the signal handlers to the 'delete' function
|
||||||
def set_handlers(self):
|
def set_handlers(self):
|
||||||
'''
|
"""
|
||||||
Trap both SIGINT and SIGTERM to gracefully clean up PID files
|
Trap both SIGINT and SIGTERM to gracefully clean up PID files
|
||||||
'''
|
"""
|
||||||
self.__handlers = {SIGINT: Signal(SIGINT, self.delete)}
|
self.__handlers = {SIGINT: Signal(SIGINT, self.delete),
|
||||||
self.__handlers = {SIGTERM: Signal(SIGTERM, self.delete)}
|
SIGTERM: Signal(SIGTERM, self.delete)}
|
||||||
|
|
||||||
#
|
#
|
||||||
# Check to see if the PID already exists
|
# Check to see if the PID already exists
|
||||||
|
@ -126,12 +132,12 @@ class Lock(object): # python 3+ 'class Lock'
|
||||||
# Stop the current process
|
# Stop the current process
|
||||||
# Delete the exiting file
|
# Delete the exiting file
|
||||||
def exists(self):
|
def exists(self):
|
||||||
'''
|
"""
|
||||||
Check to see if the PID lock file currently exists. If it does
|
Check to see if the PID lock file currently exists. If it does
|
||||||
than send a SIGTERM signal to the process defined by the value
|
than send a SIGTERM signal to the process defined by the value
|
||||||
in the lock file. Catch the keyboard interrupt exception to
|
in the lock file. Catch the keyboard interrupt exception to
|
||||||
prevent propagation if stopped by use of Ctrl-C.
|
prevent propagation if stopped by use of Ctrl-C.
|
||||||
'''
|
"""
|
||||||
if not os.path.isfile(self.path):
|
if not os.path.isfile(self.path):
|
||||||
return
|
return
|
||||||
with open(self.path, 'r') as L:
|
with open(self.path, 'r') as L:
|
||||||
|
@ -143,11 +149,11 @@ class Lock(object): # python 3+ 'class Lock'
|
||||||
#
|
#
|
||||||
# Create a lock file for this server process
|
# Create a lock file for this server process
|
||||||
def touch(self):
|
def touch(self):
|
||||||
'''
|
"""
|
||||||
If needed, create the '/tmp/mycroft' directory than open the
|
If needed, create the '/tmp/mycroft' directory than open the
|
||||||
lock file for writting and store the current process ID (PID)
|
lock file for writting and store the current process ID (PID)
|
||||||
as text.
|
as text.
|
||||||
'''
|
"""
|
||||||
if not os.path.exists(Lock.DIRECTORY):
|
if not os.path.exists(Lock.DIRECTORY):
|
||||||
os.makedirs(Lock.DIRECTORY)
|
os.makedirs(Lock.DIRECTORY)
|
||||||
with open(self.path, 'w') as L:
|
with open(self.path, 'w') as L:
|
||||||
|
@ -156,12 +162,12 @@ class Lock(object): # python 3+ 'class Lock'
|
||||||
#
|
#
|
||||||
# Create the PID file
|
# Create the PID file
|
||||||
def create(self):
|
def create(self):
|
||||||
'''
|
"""
|
||||||
Checks to see if a lock file for this service already exists,
|
Checks to see if a lock file for this service already exists,
|
||||||
if so have it killed. In either case write the process ID of
|
if so have it killed. In either case write the process ID of
|
||||||
the current service process to to the existing or newly created
|
the current service process to to the existing or newly created
|
||||||
lock file in /tmp/mycroft/
|
lock file in /tmp/mycroft/
|
||||||
'''
|
"""
|
||||||
self.exists() # check for current running process
|
self.exists() # check for current running process
|
||||||
self.touch()
|
self.touch()
|
||||||
|
|
||||||
|
@ -169,12 +175,12 @@ class Lock(object): # python 3+ 'class Lock'
|
||||||
# Delete the PID file - but only if it has not been overwritten
|
# Delete the PID file - but only if it has not been overwritten
|
||||||
# by a duplicate service application
|
# by a duplicate service application
|
||||||
def delete(self, *args):
|
def delete(self, *args):
|
||||||
'''
|
"""
|
||||||
If the PID lock file contains the PID of this process delete it.
|
If the PID lock file contains the PID of this process delete it.
|
||||||
|
|
||||||
*args: Ignored. Required as this fuction is called as a signel
|
*args: Ignored. Required as this fuction is called as a signel
|
||||||
handler.
|
handler.
|
||||||
'''
|
"""
|
||||||
try:
|
try:
|
||||||
with open(self.path, 'r') as L:
|
with open(self.path, 'r') as L:
|
||||||
pid = int(L.read())
|
pid = int(L.read())
|
||||||
|
|
|
@ -14,16 +14,16 @@
|
||||||
#
|
#
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import monotonic
|
|
||||||
from multiprocessing.pool import ThreadPool
|
from multiprocessing.pool import ThreadPool
|
||||||
|
|
||||||
from threading import Event
|
from threading import Event
|
||||||
|
|
||||||
|
import monotonic
|
||||||
from pyee import EventEmitter
|
from pyee import EventEmitter
|
||||||
from websocket import WebSocketApp
|
from websocket import WebSocketApp, WebSocketConnectionClosedException
|
||||||
|
|
||||||
from mycroft.configuration import Configuration
|
from mycroft.configuration import Configuration
|
||||||
from mycroft.messagebus.message import Message
|
from mycroft.messagebus.message import Message
|
||||||
from mycroft.util import validate_param
|
from mycroft.util import validate_param, create_echo_function
|
||||||
from mycroft.util.log import LOG
|
from mycroft.util.log import LOG
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,11 +68,19 @@ class WebsocketClient(object):
|
||||||
self.emitter.emit("close")
|
self.emitter.emit("close")
|
||||||
|
|
||||||
def on_error(self, ws, error):
|
def on_error(self, ws, error):
|
||||||
|
if isinstance(error, WebSocketConnectionClosedException):
|
||||||
|
LOG.warning('Could not send message because connection has closed')
|
||||||
|
return
|
||||||
|
|
||||||
|
LOG.exception(
|
||||||
|
'=== ' + error.__class__.__name__ + ': ' + str(error) + ' ===')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.emitter.emit('error', error)
|
self.emitter.emit('error', error)
|
||||||
self.client.close()
|
if self.client.keep_running:
|
||||||
|
self.client.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(repr(e))
|
LOG.error('Exception closing websocket: ' + repr(e))
|
||||||
LOG.warning("WS Client will reconnect in %d seconds." % self.retry)
|
LOG.warning("WS Client will reconnect in %d seconds." % self.retry)
|
||||||
time.sleep(self.retry)
|
time.sleep(self.retry)
|
||||||
self.retry = min(self.retry * 2, 60)
|
self.retry = min(self.retry * 2, 60)
|
||||||
|
@ -92,10 +100,14 @@ class WebsocketClient(object):
|
||||||
'before emitting messages')
|
'before emitting messages')
|
||||||
self.connected_event.wait()
|
self.connected_event.wait()
|
||||||
|
|
||||||
if hasattr(message, 'serialize'):
|
try:
|
||||||
self.client.send(message.serialize())
|
if hasattr(message, 'serialize'):
|
||||||
else:
|
self.client.send(message.serialize())
|
||||||
self.client.send(json.dumps(message.__dict__))
|
else:
|
||||||
|
self.client.send(json.dumps(message.__dict__))
|
||||||
|
except WebSocketConnectionClosedException:
|
||||||
|
LOG.warning('Could not send {} message because connection '
|
||||||
|
'has been closed'.format(message.type))
|
||||||
|
|
||||||
def wait_for_response(self, message, reply_type=None, timeout=None):
|
def wait_for_response(self, message, reply_type=None, timeout=None):
|
||||||
"""Send a message and wait for a response.
|
"""Send a message and wait for a response.
|
||||||
|
@ -141,7 +153,10 @@ class WebsocketClient(object):
|
||||||
self.emitter.once(event_name, func)
|
self.emitter.once(event_name, func)
|
||||||
|
|
||||||
def remove(self, event_name, func):
|
def remove(self, event_name, func):
|
||||||
self.emitter.remove_listener(event_name, func)
|
try:
|
||||||
|
self.emitter.remove_listener(event_name, func)
|
||||||
|
except ValueError as e:
|
||||||
|
LOG.warning('Failed to remove event {}: {}'.format(event_name, e))
|
||||||
|
|
||||||
def remove_all_listeners(self, event_name):
|
def remove_all_listeners(self, event_name):
|
||||||
'''
|
'''
|
||||||
|
@ -166,14 +181,11 @@ class WebsocketClient(object):
|
||||||
def echo():
|
def echo():
|
||||||
ws = WebsocketClient()
|
ws = WebsocketClient()
|
||||||
|
|
||||||
def echo(message):
|
|
||||||
LOG.info(message)
|
|
||||||
|
|
||||||
def repeat_utterance(message):
|
def repeat_utterance(message):
|
||||||
message.type = 'speak'
|
message.type = 'speak'
|
||||||
ws.emit(message)
|
ws.emit(message)
|
||||||
|
|
||||||
ws.on('message', echo)
|
ws.on('message', create_echo_function(None))
|
||||||
ws.on('recognizer_loop:utterance', repeat_utterance)
|
ws.on('recognizer_loop:utterance', repeat_utterance)
|
||||||
ws.run_forever()
|
ws.run_forever()
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ from tornado import autoreload, web, ioloop
|
||||||
from mycroft.configuration import Configuration
|
from mycroft.configuration import Configuration
|
||||||
from mycroft.lock import Lock # creates/supports PID locking file
|
from mycroft.lock import Lock # creates/supports PID locking file
|
||||||
from mycroft.messagebus.service.ws import WebsocketEventHandler
|
from mycroft.messagebus.service.ws import WebsocketEventHandler
|
||||||
from mycroft.util import validate_param
|
from mycroft.util import validate_param, reset_sigint_handler, create_daemon, \
|
||||||
|
wait_for_exit_signal
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
'debug': True
|
'debug': True
|
||||||
|
@ -27,6 +27,7 @@ settings = {
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
import tornado.options
|
import tornado.options
|
||||||
|
reset_sigint_handler()
|
||||||
lock = Lock("service")
|
lock = Lock("service")
|
||||||
tornado.options.parse_command_line()
|
tornado.options.parse_command_line()
|
||||||
|
|
||||||
|
@ -50,7 +51,9 @@ def main():
|
||||||
]
|
]
|
||||||
application = web.Application(routes, **settings)
|
application = web.Application(routes, **settings)
|
||||||
application.listen(port, host)
|
application.listen(port, host)
|
||||||
ioloop.IOLoop.instance().start()
|
create_daemon(ioloop.IOLoop.instance().start)
|
||||||
|
|
||||||
|
wait_for_exit_signal()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -910,7 +910,7 @@ class MycroftSkill(object):
|
||||||
Returns:
|
Returns:
|
||||||
str: name unique to this skill
|
str: name unique to this skill
|
||||||
"""
|
"""
|
||||||
return str(self.skill_id) + ':' + name
|
return str(self.skill_id) + ':' + (name or '')
|
||||||
|
|
||||||
def _schedule_event(self, handler, when, data=None, name=None,
|
def _schedule_event(self, handler, when, data=None, name=None,
|
||||||
repeat=None):
|
repeat=None):
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import gc
|
import gc
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
@ -26,16 +25,19 @@ from os.path import exists, join
|
||||||
import mycroft.lock
|
import mycroft.lock
|
||||||
from mycroft import MYCROFT_ROOT_PATH, dialog
|
from mycroft import MYCROFT_ROOT_PATH, dialog
|
||||||
from mycroft.api import is_paired, BackendDown
|
from mycroft.api import is_paired, BackendDown
|
||||||
|
from mycroft.client.enclosure.api import EnclosureAPI
|
||||||
from mycroft.configuration import Configuration
|
from mycroft.configuration import Configuration
|
||||||
from mycroft.messagebus.client.ws import WebsocketClient
|
from mycroft.messagebus.client.ws import WebsocketClient
|
||||||
from mycroft.messagebus.message import Message
|
from mycroft.messagebus.message import Message
|
||||||
from mycroft.skills.core import load_skill, create_skill_descriptor, \
|
from mycroft.skills.core import load_skill, create_skill_descriptor, \
|
||||||
MainModule, FallbackSkill
|
MainModule, FallbackSkill
|
||||||
from mycroft.client.enclosure.api import EnclosureAPI
|
|
||||||
from mycroft.skills.event_scheduler import EventScheduler
|
from mycroft.skills.event_scheduler import EventScheduler
|
||||||
from mycroft.skills.intent_service import IntentService
|
from mycroft.skills.intent_service import IntentService
|
||||||
from mycroft.skills.padatious_service import PadatiousService
|
from mycroft.skills.padatious_service import PadatiousService
|
||||||
from mycroft.util import connected, wait_while_speaking
|
from mycroft.util import (
|
||||||
|
connected, wait_while_speaking, reset_sigint_handler,
|
||||||
|
create_echo_function, create_daemon, wait_for_exit_signal
|
||||||
|
)
|
||||||
from mycroft.util.log import LOG
|
from mycroft.util.log import LOG
|
||||||
|
|
||||||
ws = None
|
ws = None
|
||||||
|
@ -146,8 +148,8 @@ def check_connection():
|
||||||
# Time moved by over an hour in the NTP sync. Force a reboot to
|
# Time moved by over an hour in the NTP sync. Force a reboot to
|
||||||
# prevent weird things from occcurring due to the 'time warp'.
|
# prevent weird things from occcurring due to the 'time warp'.
|
||||||
#
|
#
|
||||||
ws.emit(Message("speak", {'utterance':
|
data = {'utterance': dialog.get("time.changed.reboot")}
|
||||||
dialog.get("time.changed.reboot")}))
|
ws.emit(Message("speak", data))
|
||||||
wait_while_speaking()
|
wait_while_speaking()
|
||||||
|
|
||||||
# provide visual indicators of the reboot
|
# provide visual indicators of the reboot
|
||||||
|
@ -315,8 +317,8 @@ class SkillManager(Thread):
|
||||||
self.next_download = time.time() + 60 * MINUTES
|
self.next_download = time.time() + 60 * MINUTES
|
||||||
|
|
||||||
if res == 0 and speak:
|
if res == 0 and speak:
|
||||||
self.ws.emit(Message("speak", {'utterance':
|
data = {'utterance': dialog.get("skills updated")}
|
||||||
dialog.get("skills updated")}))
|
self.ws.emit(Message("speak", data))
|
||||||
return True
|
return True
|
||||||
elif not connected():
|
elif not connected():
|
||||||
LOG.error('msm failed, network connection not available')
|
LOG.error('msm failed, network connection not available')
|
||||||
|
@ -386,11 +388,10 @@ class SkillManager(Thread):
|
||||||
# Remove two local references that are known
|
# Remove two local references that are known
|
||||||
refs = sys.getrefcount(skill["instance"]) - 2
|
refs = sys.getrefcount(skill["instance"]) - 2
|
||||||
if refs > 0:
|
if refs > 0:
|
||||||
LOG.warning(
|
msg = ("After shutdown of {} there are still "
|
||||||
"After shutdown of {} there are still "
|
"{} references remaining. The skill "
|
||||||
"{} references remaining. The skill "
|
"won't be cleaned from memory.")
|
||||||
"won't be cleaned from memory."
|
LOG.warning(msg.format(skill['instance'].name, refs))
|
||||||
.format(skill['instance'].name, refs))
|
|
||||||
del skill["instance"]
|
del skill["instance"]
|
||||||
self.ws.emit(Message("mycroft.skills.shutdown",
|
self.ws.emit(Message("mycroft.skills.shutdown",
|
||||||
{"folder": skill_folder,
|
{"folder": skill_folder,
|
||||||
|
@ -478,13 +479,6 @@ class SkillManager(Thread):
|
||||||
# Pause briefly before beginning next scan
|
# Pause briefly before beginning next scan
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
# Do a clean shutdown of all skills
|
|
||||||
for skill in self.loaded_skills:
|
|
||||||
try:
|
|
||||||
self.loaded_skills[skill]['instance'].shutdown()
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def send_skill_list(self, message=None):
|
def send_skill_list(self, message=None):
|
||||||
"""
|
"""
|
||||||
Send list of loaded skills.
|
Send list of loaded skills.
|
||||||
|
@ -504,6 +498,15 @@ class SkillManager(Thread):
|
||||||
""" Tell the manager to shutdown """
|
""" Tell the manager to shutdown """
|
||||||
self._stop_event.set()
|
self._stop_event.set()
|
||||||
|
|
||||||
|
# Do a clean shutdown of all skills
|
||||||
|
for name, skill_info in self.loaded_skills.items():
|
||||||
|
instance = skill_info.get('instance')
|
||||||
|
if instance:
|
||||||
|
try:
|
||||||
|
instance.shutdown()
|
||||||
|
except Exception:
|
||||||
|
LOG.exception('Shutting down skill: ' + name)
|
||||||
|
|
||||||
def handle_converse_request(self, message):
|
def handle_converse_request(self, message):
|
||||||
""" Check if the targeted skill id can handle conversation
|
""" Check if the targeted skill id can handle conversation
|
||||||
|
|
||||||
|
@ -538,46 +541,31 @@ class SkillManager(Thread):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global ws
|
global ws
|
||||||
|
reset_sigint_handler()
|
||||||
# Create PID file, prevent multiple instancesof this service
|
# Create PID file, prevent multiple instancesof this service
|
||||||
mycroft.lock.Lock('skills')
|
mycroft.lock.Lock('skills')
|
||||||
# Connect this Skill management process to the websocket
|
# Connect this Skill management process to the websocket
|
||||||
ws = WebsocketClient()
|
ws = WebsocketClient()
|
||||||
Configuration.init(ws)
|
Configuration.init(ws)
|
||||||
ignore_logs = Configuration.get().get("ignore_logs")
|
|
||||||
|
|
||||||
# Listen for messages and echo them for logging
|
ws.on('message', create_echo_function('SKILLS'))
|
||||||
def _echo(message):
|
|
||||||
try:
|
|
||||||
_message = json.loads(message)
|
|
||||||
|
|
||||||
if _message.get("type") in ignore_logs:
|
|
||||||
return
|
|
||||||
|
|
||||||
if _message.get("type") == "registration":
|
|
||||||
# do not log tokens from registration messages
|
|
||||||
_message["data"]["token"] = None
|
|
||||||
message = json.dumps(_message)
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
LOG('SKILLS').debug(message)
|
|
||||||
|
|
||||||
ws.on('message', _echo)
|
|
||||||
# Startup will be called after websocket is fully live
|
# Startup will be called after websocket is fully live
|
||||||
ws.once('open', _starting_up)
|
ws.once('open', _starting_up)
|
||||||
ws.run_forever()
|
|
||||||
|
create_daemon(ws.run_forever)
|
||||||
|
wait_for_exit_signal()
|
||||||
|
shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
if event_scheduler:
|
||||||
|
event_scheduler.shutdown()
|
||||||
|
|
||||||
|
# Terminate all running threads that update skills
|
||||||
|
if skill_manager:
|
||||||
|
skill_manager.stop()
|
||||||
|
skill_manager.join()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
main()
|
||||||
main()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
if event_scheduler:
|
|
||||||
event_scheduler.shutdown()
|
|
||||||
|
|
||||||
# Terminate all running threads that update skills
|
|
||||||
if skill_manager:
|
|
||||||
skill_manager.stop()
|
|
||||||
skill_manager.join()
|
|
||||||
|
|
||||||
finally:
|
|
||||||
sys.exit()
|
|
||||||
|
|
|
@ -12,13 +12,19 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
from __future__ import absolute_import
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from threading import Thread
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
import psutil
|
import psutil
|
||||||
from stat import S_ISREG, ST_MTIME, ST_MODE, ST_SIZE
|
from stat import S_ISREG, ST_MTIME, ST_MODE, ST_SIZE
|
||||||
|
|
||||||
|
import signal as sig
|
||||||
|
|
||||||
import mycroft.audio
|
import mycroft.audio
|
||||||
import mycroft.configuration
|
import mycroft.configuration
|
||||||
from mycroft.util.format import nice_number
|
from mycroft.util.format import nice_number
|
||||||
|
@ -266,3 +272,53 @@ def stop_speaking():
|
||||||
def get_arch():
|
def get_arch():
|
||||||
""" Get architecture string of system. """
|
""" Get architecture string of system. """
|
||||||
return os.uname()[4]
|
return os.uname()[4]
|
||||||
|
|
||||||
|
|
||||||
|
def reset_sigint_handler():
|
||||||
|
"""
|
||||||
|
Reset the sigint handler to the default. This fixes KeyboardInterrupt
|
||||||
|
not getting raised when started via start-mycroft.sh
|
||||||
|
"""
|
||||||
|
sig.signal(sig.SIGINT, sig.default_int_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def create_daemon(target, args=(), kwargs=None):
|
||||||
|
"""Helper to quickly create and start a thread with daemon = True"""
|
||||||
|
t = Thread(target=target, args=args, kwargs=kwargs)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_exit_signal():
|
||||||
|
"""Blocks until KeyboardInterrupt is received"""
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
sleep(100)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def create_echo_function(name, whitelist=None):
|
||||||
|
from mycroft.configuration import Configuration
|
||||||
|
blacklist = Configuration.get().get("ignore_logs")
|
||||||
|
|
||||||
|
def echo(message):
|
||||||
|
"""Listen for messages and echo them for logging"""
|
||||||
|
try:
|
||||||
|
js_msg = json.loads(message)
|
||||||
|
|
||||||
|
if whitelist and js_msg.get("type") not in whitelist:
|
||||||
|
return
|
||||||
|
|
||||||
|
if blacklist and js_msg.get("type") in blacklist:
|
||||||
|
return
|
||||||
|
|
||||||
|
if js_msg.get("type") == "registration":
|
||||||
|
# do not log tokens from registration messages
|
||||||
|
js_msg["data"]["token"] = None
|
||||||
|
message = json.dumps(js_msg)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
LOG(name).debug(message)
|
||||||
|
return echo
|
||||||
|
|
|
@ -53,7 +53,7 @@ function end-process() {
|
||||||
|
|
||||||
if process-running $1 ; then
|
if process-running $1 ; then
|
||||||
pid=$( ps aux | grep "[p]ython .*${1}/main.py" | awk '{print $2}' )
|
pid=$( ps aux | grep "[p]ython .*${1}/main.py" | awk '{print $2}' )
|
||||||
kill ${pid}
|
kill -SIGINT ${pid}
|
||||||
|
|
||||||
c=1
|
c=1
|
||||||
while [ $c -le 20 ]
|
while [ $c -le 20 ]
|
||||||
|
@ -67,6 +67,7 @@ function end-process() {
|
||||||
done
|
done
|
||||||
|
|
||||||
if process-running $1 ; then
|
if process-running $1 ; then
|
||||||
|
echo "Killing $1..."
|
||||||
kill -9 ${pid}
|
kill -9 ${pid}
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
Loading…
Reference in New Issue