class WebSocketHandler(tornado.websocket.WebSocketHandler): peer = "unknown" def create_internal_emitter(self): # connect to mycroft internal websocket self.emitter = WebsocketClient() self.register_internal_messages() self.emitter_thread = Thread(target=self.connect_to_internal_emitter) self.emitter_thread.setDaemon(True) self.emitter_thread.start() def register_internal_messages(self): # catch all messages self.emitter.on('speak', self.handle_speak) def connect_to_internal_emitter(self): self.emitter.run_forever() def open(self): LOG.info('Client IP: ' + self.request.remote_ip) self.peer = self.request.remote_ip clients[self.peer] = self self.create_internal_emitter() self.write_message("Welcome to Jarbas Web Client") def on_message(self, message): utterance = message.strip() LOG.info("Utterance : " + utterance) if utterance: if utterance == '"mic_on"': create_signal('startListening') else: data = {"utterances": [utterance], "lang": lang} context = { "source": self.peer, "destinatary": "skills", "client_name": platform, "peer": self.peer } self.emitter.emit( Message("recognizer_loop:utterance", data, context)) def handle_speak(self, event): if event.context.get("client_name", platform) == platform: peer = event.context.get("peer", "") if peer == self.peer: self.write_message(event.data['utterance']) def on_close(self): global clients self.emitter.remove("speak", self.handle_speak) clients.pop(self.peer)
class Enclosure: """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): self.__init_serial() self.client = WebsocketClient() self.reader = EnclosureReader(self.serial, self.client) self.writer = EnclosureWriter(self.serial, self.client) self.eyes = EnclosureEyes(self.client, self.writer) self.mouth = EnclosureMouth(self.client, self.writer) self.system = EnclosureArduino(self.client, self.writer) self.weather = EnclosureWeather(self.client, self.writer) self.__register_events() def setup(self): must_upload = self.config.get('must_upload') if must_upload is not None and str2bool(must_upload): ConfigurationManager.set('enclosure', 'must_upload', False) time.sleep(5) self.client.emit(Message("speak", metadata={ 'utterance': "I am currently uploading to the arduino."})) self.client.emit(Message("speak", metadata={ 'utterance': "I will be finished in just a moment."})) self.upload_hex() self.client.emit(Message("speak", metadata={ 'utterance': "Arduino programing complete."})) must_start_test = self.config.get('must_start_test') if must_start_test is not None and str2bool(must_start_test): ConfigurationManager.set('enclosure', 'must_start_test', False) time.sleep(0.5) # Ensure arduino has booted self.client.emit(Message("speak", metadata={ 'utterance': "Begining hardware self test."})) self.writer.write("test.begin") @staticmethod def upload_hex(): old_path = os.getcwd() try: os.chdir('/opt/enclosure/') subprocess.check_call('./upload.sh') finally: os.chdir(old_path) def __init_serial(self): try: self.config = ConfigurationManager.get().get("enclosure") self.port = self.config.get("port") self.rate = int(self.config.get("rate")) self.timeout = int(self.config.get("timeout")) self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOGGER.info( "Connected to: " + self.port + " rate: " + str(self.rate) + " timeout: " + str(self.timeout)) except: LOGGER.error( "It is not possible to connect to serial port: " + self.port) raise def __register_events(self): self.client.on('mycroft.paired', self.__update_events) self.client.on('enclosure.mouth.listeners', self.__mouth_listeners) self.__register_mouth_events() def __mouth_listeners(self, event=None): if event and event.metadata: active = event.metadata['active'] if active: self.__register_mouth_events() else: self.__remove_mouth_events() def __register_mouth_events(self): self.client.on('recognizer_loop:record_begin', self.mouth.listen) self.client.on('recognizer_loop:record_end', self.mouth.reset) self.client.on('recognizer_loop:audio_output_start', self.mouth.talk) self.client.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self): self.client.remove('recognizer_loop:record_begin', self.mouth.listen) self.client.remove('recognizer_loop:record_end', self.mouth.reset) self.client.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.client.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __update_events(self, event=None): if event and event.metadata: if event.metadata.get('paired', False): self.__register_mouth_events() else: self.__remove_mouth_events() def run(self): try: self.client.run_forever() except Exception as e: LOGGER.error("Client error: {0}".format(e)) self.stop() def stop(self): self.writer.stop() self.reader.stop() self.serial.close()
class Enclosure: """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): self.__init_serial() self.client = WebsocketClient() self.reader = EnclosureReader(self.serial, self.client) self.writer = EnclosureWriter(self.serial, self.client) self.eyes = EnclosureEyes(self.client, self.writer) self.mouth = EnclosureMouth(self.client, self.writer) self.system = EnclosureArduino(self.client, self.writer) self.weather = EnclosureWeather(self.client, self.writer) self.__register_events() def __init_serial(self): try: self.config = ConfigurationManager.get().get("enclosure") self.port = self.config.get("port") self.rate = int(self.config.get("rate")) self.timeout = int(self.config.get("timeout")) self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOGGER.info( "Connected to: " + self.port + " rate: " + str(self.rate) + " timeout: " + str(self.timeout)) except: LOGGER.error( "It is not possible to connect to serial port: " + self.port) raise def __register_events(self): self.client.on('mycroft.paired', self.__update_events) self.client.on('enclosure.mouth.listeners', self.__mouth_listeners) self.__register_mouth_events() def __mouth_listeners(self, event=None): if event and event.metadata: active = event.metadata['active'] if active: self.__register_mouth_events() else: self.__remove_mouth_events() def __register_mouth_events(self): self.client.on('recognizer_loop:record_begin', self.mouth.listen) self.client.on('recognizer_loop:record_end', self.mouth.reset) self.client.on('recognizer_loop:audio_output_start', self.mouth.talk) self.client.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self): self.client.remove('recognizer_loop:record_begin', self.mouth.listen) self.client.remove('recognizer_loop:record_end', self.mouth.reset) self.client.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.client.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __update_events(self, event=None): if event and event.metadata: if event.metadata.get('paired', False): self.__register_mouth_events() else: self.__remove_mouth_events() def run(self): try: self.client.run_forever() except Exception as e: LOGGER.error("Client error: {0}".format(e)) self.stop() def stop(self): self.writer.stop() self.reader.stop() self.serial.close()
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ _last_internet_notification = 0 def __init__(self): self.ws = WebsocketClient() ConfigurationManager.init(self.ws) self.config = ConfigurationManager.get().get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws) self.writer = EnclosureWriter(self.serial, self.ws) # Send a message to the Arduino across the serial line asking # for a reply with version info. self.writer.write("system.version") # When the Arduino responds, it will generate this message self.ws.on("enclosure.started", self.on_arduino_responded) self.arduino_responded = False # Start a 5 second timer. If the serial port hasn't received # any acknowledgement of the "system.version" within those # 5 seconds, assume there is nothing on the other end (e.g. # we aren't running a Mark 1 with an Arduino) Timer(5, self.check_for_response).start() # Notifications from mycroft-core self.ws.on("enclosure.notify.no_internet", self.on_no_internet) def on_arduino_responded(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.arduino_responded = True # verify internet connection and prompt user on bootup if needed if not connected(): # We delay this for several seconds to ensure that the other # clients are up and connected to the messagebus in order to # receive the "speak". This was sometimes happening too # quickly and the user wasn't notified what to do. Timer(5, self.on_no_internet).start() def on_no_internet(self, event=None): if connected(): # One last check to see if connection was established return if time.time()-Enclosure._last_internet_notification < 30: # don't bother the user with multiple notifications with 30 secs return Enclosure._last_internet_notification = time.time() # TODO: This should go into EnclosureMark1 subclass of Enclosure. # Handle the translation within that code. self.ws.emit(Message("speak", { 'utterance': "This device is not connected to the Internet. " "Either plug in a network cable or hold the button " "on top for two seconds, then select wifi from the " "menu"})) def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + self.port) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def check_for_response(self): if not self.arduino_responded: # There is nothing on the other end of the serial port # close these serial-port readers and this process self.writer.stop() self.reader.stop() self.serial.close() self.ws.close()
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): self.ws = WebsocketClient() ConfigurationManager.init(self.ws) self.config = ConfigurationManager.get().get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws) self.writer = EnclosureWriter(self.serial, self.ws) self.writer.write("system.version") self.ws.on("enclosure.start", self.start) self.started = False Timer(5, self.stop).start() # WHY? This at least # needs an explanation, this is non-obvious behavior def start(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.started = True def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url(url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + self.port) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def stop(self): if not self.started: self.writer.stop() self.reader.stop() self.serial.close() self.ws.close()
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ _last_internet_notification = 0 def __init__(self): self.ws = WebsocketClient() self.ws.on("open", self.on_ws_open) Configuration.init(self.ws) global_config = Configuration.get() self.lang = global_config['lang'] self.config = global_config.get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws, self.lang) self.writer = EnclosureWriter(self.serial, self.ws) # initiates the web sockets on display manager # NOTE: this is a temporary place to initiate display manager sockets initiate_display_manager_ws() def on_ws_open(self, event=None): # Mark 1 auto-detection: # # Prepare to receive message when the Arduino responds to the # following "system.version" self.ws.on("enclosure.started", self.on_arduino_responded) self.arduino_responded = False # Send a message to the Arduino across the serial line asking # for a reply with version info. self.writer.write("system.version") # Start a 5 second timer. If the serial port hasn't received # any acknowledgement of the "system.version" within those # 5 seconds, assume there is nothing on the other end (e.g. # we aren't running a Mark 1 with an Arduino) Timer(5, self.check_for_response).start() # Notifications from mycroft-core self.ws.on("enclosure.notify.no_internet", self.on_no_internet) def on_arduino_responded(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.arduino_responded = True # verify internet connection and prompt user on bootup if needed if not connected(): # We delay this for several seconds to ensure that the other # clients are up and connected to the messagebus in order to # receive the "speak". This was sometimes happening too # quickly and the user wasn't notified what to do. Timer(5, self._do_net_check).start() Timer(60, self._hack_check_for_duplicates).start() def on_no_internet(self, event=None): if connected(): # One last check to see if connection was established return if time.time() - Enclosure._last_internet_notification < 30: # don't bother the user with multiple notifications with 30 secs return Enclosure._last_internet_notification = time.time() # TODO: This should go into EnclosureMark1 subclass of Enclosure. if has_been_paired(): # Handle the translation within that code. self.ws.emit( Message( "speak", { 'utterance': "This device is not connected to the Internet. " "Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu" })) else: # enter wifi-setup mode automatically self.ws.emit(Message('system.wifi.setup', {'lang': self.lang})) def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url(url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + str(self.port)) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def check_for_response(self): if not self.arduino_responded: # There is nothing on the other end of the serial port # close these serial-port readers and this process self.writer.stop() self.reader.stop() self.serial.close() self.ws.close() def _handle_pairing_complete(self, Message): """ Handler for 'mycroft.paired', unmutes the mic after the pairing is complete. """ self.ws.emit(Message("mycroft.mic.unmute")) def _do_net_check(self): # TODO: This should live in the derived Enclosure, e.g. Enclosure_Mark1 LOG.info("Checking internet connection") if not connected(): # and self.conn_monitor is None: if has_been_paired(): # TODO: Enclosure/localization self.speak("This unit is not connected to the Internet. " "Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu") else: # Begin the unit startup process, this is the first time it # is being run with factory defaults. # TODO: This logic should be in Enclosure_Mark1 # TODO: Enclosure/localization # Don't listen to mic during this out-of-box experience self.ws.emit(Message("mycroft.mic.mute")) # Setup handler to unmute mic at the end of on boarding # i.e. after pairing is complete self.ws.once('mycroft.paired', self._handle_pairing_complete) self.speak(mycroft.dialog.get('mycroft.intro')) wait_while_speaking() time.sleep(2) # a pause sounds better than just jumping in # Kick off wifi-setup automatically data = {'allow_timeout': False, 'lang': self.lang} self.ws.emit(Message('system.wifi.setup', data)) def _hack_check_for_duplicates(self): # TEMPORARY HACK: Look for multiple instance of the # mycroft-speech-client and/or mycroft-skills services, which could # happen when upgrading a shipping Mark 1 from release 0.8.17 or # before. When found, force the unit to reboot. import psutil LOG.info("Hack to check for duplicate service instances") count_instances = 0 needs_reboot = False for process in psutil.process_iter(): if process.cmdline() == [ 'python2.7', '/usr/local/bin/mycroft-speech-client' ]: count_instances += 1 if (count_instances > 1): LOG.info("Duplicate mycroft-speech-client found") needs_reboot = True count_instances = 0 for process in psutil.process_iter(): if process.cmdline() == [ 'python2.7', '/usr/local/bin/mycroft-skills' ]: count_instances += 1 if (count_instances > 1): LOG.info("Duplicate mycroft-skills found") needs_reboot = True if needs_reboot: LOG.info("Hack reboot...") self.reader.process("unit.reboot") self.ws.emit(Message("enclosure.eyes.spin")) self.ws.emit(Message("enclosure.mouth.reset"))
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ _last_internet_notification = 0 def __init__(self): self.ws = WebsocketClient() self.ws.on("open", self.on_ws_open) Configuration.init(self.ws) global_config = Configuration.get() self.lang = global_config['lang'] self.config = global_config.get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws, self.lang) self.writer = EnclosureWriter(self.serial, self.ws) # initiates the web sockets on display manager # NOTE: this is a temporary place to initiate display manager sockets initiate_display_manager_ws() def on_ws_open(self, event=None): # Mark 1 auto-detection: # # Prepare to receive message when the Arduino responds to the # following "system.version" self.ws.on("enclosure.started", self.on_arduino_responded) self.arduino_responded = False # Send a message to the Arduino across the serial line asking # for a reply with version info. self.writer.write("system.version") # Start a 5 second timer. If the serial port hasn't received # any acknowledgement of the "system.version" within those # 5 seconds, assume there is nothing on the other end (e.g. # we aren't running a Mark 1 with an Arduino) Timer(5, self.check_for_response).start() # Notifications from mycroft-core self.ws.on("enclosure.notify.no_internet", self.on_no_internet) def on_arduino_responded(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.arduino_responded = True # verify internet connection and prompt user on bootup if needed if not connected(): # We delay this for several seconds to ensure that the other # clients are up and connected to the messagebus in order to # receive the "speak". This was sometimes happening too # quickly and the user wasn't notified what to do. Timer(5, self._do_net_check).start() Timer(60, self._hack_check_for_duplicates).start() def on_no_internet(self, event=None): if connected(): # One last check to see if connection was established return if time.time() - Enclosure._last_internet_notification < 30: # don't bother the user with multiple notifications with 30 secs return Enclosure._last_internet_notification = time.time() # TODO: This should go into EnclosureMark1 subclass of Enclosure. if has_been_paired(): # Handle the translation within that code. self.ws.emit(Message("speak", { 'utterance': "This device is not connected to the Internet. " "Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu"})) else: # enter wifi-setup mode automatically self.ws.emit(Message('system.wifi.setup', {'lang': self.lang})) def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: "+str(self.port)) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def check_for_response(self): if not self.arduino_responded: # There is nothing on the other end of the serial port # close these serial-port readers and this process self.writer.stop() self.reader.stop() self.serial.close() self.ws.close() def _handle_pairing_complete(self, Message): """ Handler for 'mycroft.paired', unmutes the mic after the pairing is complete. """ self.ws.emit(Message("mycroft.mic.unmute")) def _do_net_check(self): # TODO: This should live in the derived Enclosure, e.g. Enclosure_Mark1 LOG.info("Checking internet connection") if not connected(): # and self.conn_monitor is None: if has_been_paired(): # TODO: Enclosure/localization self.speak("This unit is not connected to the Internet. " "Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu") else: # Begin the unit startup process, this is the first time it # is being run with factory defaults. # TODO: This logic should be in Enclosure_Mark1 # TODO: Enclosure/localization # Don't listen to mic during this out-of-box experience self.ws.emit(Message("mycroft.mic.mute")) # Setup handler to unmute mic at the end of on boarding # i.e. after pairing is complete self.ws.once('mycroft.paired', self._handle_pairing_complete) self.speak(mycroft.dialog.get('mycroft.intro')) wait_while_speaking() time.sleep(2) # a pause sounds better than just jumping in # Kick off wifi-setup automatically data = {'allow_timeout': False, 'lang': self.lang} self.ws.emit(Message('system.wifi.setup', data)) def _hack_check_for_duplicates(self): # TEMPORARY HACK: Look for multiple instance of the # mycroft-speech-client and/or mycroft-skills services, which could # happen when upgrading a shipping Mark 1 from release 0.8.17 or # before. When found, force the unit to reboot. import psutil LOG.info("Hack to check for duplicate service instances") count_instances = 0 needs_reboot = False for process in psutil.process_iter(): if process.cmdline() == ['python2.7', '/usr/local/bin/mycroft-speech-client']: count_instances += 1 if (count_instances > 1): LOG.info("Duplicate mycroft-speech-client found") needs_reboot = True count_instances = 0 for process in psutil.process_iter(): if process.cmdline() == ['python2.7', '/usr/local/bin/mycroft-skills']: count_instances += 1 if (count_instances > 1): LOG.info("Duplicate mycroft-skills found") needs_reboot = True if needs_reboot: LOG.info("Hack reboot...") self.reader.process("unit.reboot") self.ws.emit(Message("enclosure.eyes.spin")) self.ws.emit(Message("enclosure.mouth.reset"))
class Enclosure: """ Serves as a communication interface between Arduino and Mycroft Core. This interface is currently designed to shut itself down when run on a Raspberry Pi that is not connected to a Mycroft Mark 1 board on the serial port. So it can safely be started on any Raspberry Pi. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): resetSelf = False try: self.config = ConfigurationManager.get().get("enclosure") platform = self.config.get('platform') if platform is None: # Use the serial port to check if this is a Mycroft # Mark 1 unit. platform = self.detect_platform() if platform == 'unknown': # Since this is a semi-permanent detection, be # certain! Sometimes noise on the serial line # causes a faulty mis-detection on a real # Mycroft Mark 1 platform = self.detect_platform() ConfigurationManager.set('enclosure', 'platform', platform) # After a platform detection, the system is usually # already active, so the message from the loop # has already gone by on the messagebus. So self reset. resetSelf = True except Exception as e: self.disconnect() raise Exception("Exception: Unable to determine platform\n" + str(e)) LOGGER.info("Platform = '" + platform + "'") if platform == "mycroft_mark_1": # We are a mycroft_mark_1 unit, start up the # enclosure client to communicate over the # serial port self.__init_serial() else: self.disconnect() raise Exception("Exception: Not a Mycroft Mark 1, shutting down") self.client = WebsocketClient() self.reader = EnclosureReader(self.serial, self.client) self.writer = EnclosureWriter(self.serial, self.client) # Create helpers to handle the various parts of the enclosure self.eyes = EnclosureEyes(self.client, self.writer) self.mouth = EnclosureMouth(self.client, self.writer) self.system = EnclosureArduino(self.client, self.writer) # TODO: Remove EnclosureWeather once the Skill can send images # directly to the EnclosureAPI. self.weather = EnclosureWeather(self.client, self.writer) self.__register_events() if resetSelf: self.__handle_reset(None) def detect_platform(self): LOGGER.info("Auto-detecting platform") try: self.__init_serial() # Thoroughly flush the serial port. Since this happens # right after boot, sometimes there is junk on the serial # port line from electrical noise on a Pi. time.sleep(3) self.serial.flushInput() self.serial.flushOutput() # Write "system.ping" self.serial.write("system.ping") time.sleep(1) # Now check to see if we got a response from the ping command # command we just sent. Remember, there might not be a Mark 1 # Arduino on the other side of the serial port. data = self.serial.readline() LOGGER.info("Serial response: '" + data + "'") # A Mycroft Arduino echos the output to the serial line # prefixed by "Command: ". This assumes only a Mycroft # Arduino responds to this, which is probably a dubious # assumption, but a decent enough auto-detect for now. if "Command: system.ping" in data: # We have a Mycroft Mark 1! # The Arduino returns a version number in the response # with recent builds, but not with older builds. # Empty the serial queue of all responses to the ping. time.sleep(0.5) self.serial.flushInput() return "mycroft_mark_1" except: pass # Likely running on a generic Raspberry Pi or Linux desktop return "unknown" def setup(self): must_upload = self.config.get('must_upload') if must_upload is not None and str2bool(must_upload): ConfigurationManager.set('enclosure', 'must_upload', False) time.sleep(5) self.client.emit(Message("speak", metadata={ 'utterance': "I am currently uploading to the arduino."})) self.client.emit(Message("speak", metadata={ 'utterance': "I will be finished in just a moment."})) self.upload_hex() self.client.emit(Message("speak", metadata={ 'utterance': "Arduino programing complete."})) must_start_test = self.config.get('must_start_test') if must_start_test is not None and str2bool(must_start_test): ConfigurationManager.set('enclosure', 'must_start_test', False) time.sleep(0.5) # Ensure arduino has booted self.client.emit(Message("speak", metadata={ 'utterance': "Begining hardware self test."})) self.writer.write("test.begin") @staticmethod def upload_hex(): old_path = os.getcwd() try: os.chdir('/opt/enclosure/') subprocess.check_call('./upload.sh') finally: os.chdir(old_path) def __init_serial(self): if getattr(self, 'serial', None) is not None: return # already initialized LOGGER.info("Opening serial port") try: self.port = self.config.get("port") self.rate = int(self.config.get("rate")) self.timeout = int(self.config.get("timeout")) self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOGGER.info( "Connected to: " + self.port + " rate: " + str(self.rate) + " timeout: " + str(self.timeout)) except: LOGGER.error( "It is not possible to connect to serial port: " + self.port) raise def __register_events(self): self.client.on('mycroft.paired', self.__update_events) self.client.on('enclosure.reset', self.__handle_reset) self.client.on('enclosure.mouth.listeners', self.__mouth_listeners) self.__register_mouth_events() def __mouth_listeners(self, event=None): if event and event.metadata: active = event.metadata['active'] if active: self.__register_mouth_events() else: self.__remove_mouth_events() def __register_mouth_events(self): self.client.on('recognizer_loop:record_begin', self.mouth.listen) self.client.on('recognizer_loop:record_end', self.mouth.reset) self.client.on('recognizer_loop:audio_output_start', self.mouth.talk) self.client.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self): self.client.remove('recognizer_loop:record_begin', self.mouth.listen) self.client.remove('recognizer_loop:record_end', self.mouth.reset) self.client.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.client.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __handle_reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def __update_events(self, event=None): if event and event.metadata: if event.metadata.get('paired', False): self.__register_mouth_events() else: self.__remove_mouth_events() def run(self): try: self.client.run_forever() except Exception as e: LOGGER.error("Client error: {0}".format(e)) self.stop() def disconnect(self): if getattr(self, 'serial', None) is not None: self.serial.close() self.serial = None def stop(self): self.writer.stop() self.reader.stop() self.disconnect()
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ _last_internet_notification = 0 def __init__(self): self.ws = WebsocketClient() ConfigurationManager.init(self.ws) self.config = ConfigurationManager.instance().get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws) self.writer = EnclosureWriter(self.serial, self.ws) # Send a message to the Arduino across the serial line asking # for a reply with version info. self.writer.write("system.version") # When the Arduino responds, it will generate this message self.ws.on("enclosure.started", self.on_arduino_responded) self.arduino_responded = False # Start a 5 second timer. If the serial port hasn't received # any acknowledgement of the "system.version" within those # 5 seconds, assume there is nothing on the other end (e.g. # we aren't running a Mark 1 with an Arduino) Timer(5, self.check_for_response).start() # Notifications from mycroft-core self.ws.on("enclosure.notify.no_internet", self.on_no_internet) def on_arduino_responded(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.arduino_responded = True # verify internet connection and prompt user on bootup if needed if not connected(): # We delay this for several seconds to ensure that the other # clients are up and connected to the messagebus in order to # receive the "speak". This was sometimes happening too # quickly and the user wasn't notified what to do. Timer(5, self._do_net_check).start() def on_no_internet(self, event=None): if connected(): # One last check to see if connection was established return if time.time()-Enclosure._last_internet_notification < 30: # don't bother the user with multiple notifications with 30 secs return Enclosure._last_internet_notification = time.time() # TODO: This should go into EnclosureMark1 subclass of Enclosure. if has_been_paired(): # Handle the translation within that code. self.ws.emit(Message("speak", { 'utterance': "This device is not connected to the Internet. " "Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu"})) else: # enter wifi-setup mode automatically self.ws.emit(Message("mycroft.wifi.start")) def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + self.port) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def check_for_response(self): if not self.arduino_responded: # There is nothing on the other end of the serial port # close these serial-port readers and this process self.writer.stop() self.reader.stop() self.serial.close() self.ws.close() def _do_net_check(self): # TODO: This should live in the derived Enclosure, e.g. Enclosure_Mark1 LOG.info("Checking internet connection") if not connected(): # and self.conn_monitor is None: if has_been_paired(): # TODO: Enclosure/localization self.ws.emit(Message("speak", { 'utterance': "This unit is not connected to the Internet." " Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu" })) else: # Begin the unit startup process, this is the first time it # is being run with factory defaults. # TODO: This logic should be in Enclosure_Mark1 # TODO: Enclosure/localization # Don't listen to mic during this out-of-box experience self.ws.emit(Message("mycroft.mic.mute", None)) # Kick off wifi-setup automatically self.ws.emit(Message("mycroft.wifi.start", {'msg': "Hello I am Mycroft, your new " "assistant. To assist you I need to be " "connected to the internet. You can " "either plug me in with a network cable," " or use wifi. To setup wifi ", 'allow_timeout': False}))
class ResponderBackend(object): """ Base class for all responder implementations. set response handlers for specific message_types """ def __init__(self, name=None, emitter=None, logger=None): """ initialize responder args: name(str): name identifier emitter (WebsocketClient): mycroft messagebus websocket logger (logger) : custom logger if desired """ if name is None: self.name = "ResponderBackend" else: self.name = name if emitter is None: self.emitter = WebsocketClient() def connect(): self.emitter.run_forever() ws_thread = Thread(target=connect) ws_thread.setDaemon(True) ws_thread.start() else: self.emitter = emitter self.response_type = "default.reply" if self.responder: self.responder.shutdown() self.responder = None self.callback = None if logger is None: self.logger = LOG else: self.logger = logger self.config = Configuration.get().get(self.name, {}) self.events = [] def update_response_data(self, response_data=None, response_context=None): """ change the data of the response to be sent when queried Args: message_data (dict): response message data message_context (dict): response message context """ if self.responder is not None: self.responder.update_response(response_data, response_context) def set_response_handler(self, trigger_message, callback, response_data=None, response_context=None): """ prepare responder for sending, register answers args: trigger_message (str): message_type that triggers response callback (function): to execute on message_type response_data (dict) : default data of the response message response_context (dict) : default context of the response message """ # update message context response_context = response_context or {} response_context["source"] = self.name response_context["triggered_by"] = trigger_message # generate reply message if ".request" in trigger_message: self.response_type = trigger_message.replace(".request", ".reply") else: self.response_type = trigger_message + ".reply" self.responder = BusResponder(self.emitter, self.response_type, response_data, response_context, []) self.callback = callback self.emitter.on(trigger_message, self._respond) self.events.append(trigger_message) def _respond(self, message): """ on query execute callback and send a response args: message (Message) : query message """ try: if self.callback: self.callback(message) except Exception as e: self.logger.error(e) self.responder.respond(message) def shutdown(self): """ remove all listeners """ for event in self.events: self.emitter.remove(event, self._respond) if self.responder: self.responder.shutdown() self.responder = None
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): self.ws = WebsocketClient() ConfigurationManager.init(self.ws) self.config = ConfigurationManager.get().get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws) self.writer = EnclosureWriter(self.serial, self.ws) self.writer.write("system.version") self.ws.on("enclosure.start", self.start) self.started = False Timer(5, self.stop).start() def start(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() #self.update() self.test() self.started = True def update(self): if self.config.get("update"): try: self.speak("Upgrading enclosure version") subprocess.check_call("/opt/enclosure/upload.sh") self.speak("Enclosure update completed") ConfigurationManager.save({"enclosure": {"update": False}}) except: self.speak("I cannot upgrade right now, I'll try later") def test(self): if self.config.get("test"): self.speak("Beginning hardware test") self.writer.write("test.begin") ConfigurationManager.save({"enclosure": {"test": False}}) def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url(url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + self.port) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def stop(self): if not self.started: self.writer.stop() self.reader.stop() self.serial.close() self.ws.close()
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): self.ws = WebsocketClient() ConfigurationManager.init(self.ws) self.config = ConfigurationManager.get().get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws) self.writer = EnclosureWriter(self.serial, self.ws) self.writer.write("system.version") self.ws.on("enclosure.start", self.start) self.started = False Timer(5, self.stop).start() # WHY? This at least needs an explaination, this is non-obvious behavior def start(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.started = True def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + self.port) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def stop(self): if not self.started: self.writer.stop() self.reader.stop() self.serial.close() self.ws.close()
class Enclosure: """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): self.__init_serial() self.client = WebsocketClient() self.reader = EnclosureReader(self.serial, self.client) self.writer = EnclosureWriter(self.serial, self.client) self.eyes = EnclosureEyes(self.client, self.writer) self.mouth = EnclosureMouth(self.client, self.writer) self.system = EnclosureArduino(self.client, self.writer) self.__register_events() def __init_serial(self): try: self.config = ConfigurationManager.get_config().get("enclosure") self.port = self.config.get("port") self.rate = int(self.config.get("rate")) self.timeout = int(self.config.get("timeout")) self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOGGER.info( "Connected to: " + self.port + " rate: " + str(self.rate) + " timeout: " + str(self.timeout)) except: LOGGER.error( "It is not possible to connect to serial port: " + self.port) raise def __register_events(self): self.client.on('mycroft.paired', self.__update_events) self.client.on('recognizer_loop:wakeword', self.eyes.blink) self.__register_mouth_events() def __register_mouth_events(self): self.client.on('recognizer_loop:listening', self.mouth.listen) self.client.on('recognizer_loop:audio_output_start', self.mouth.talk) self.client.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self): self.client.remove('recognizer_loop:listening', self.mouth.listen) self.client.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.client.remove('recognizer_loop:audio_output_end', self.mouth.reset) self.mouth.reset() def __update_events(self, event=None): if event and event.metadata: if event.metadata.get('paired', False): self.__register_mouth_events() else: self.__remove_mouth_events() def run(self): try: self.client.run_forever() except Exception as e: LOGGER.error("Client error: {0}".format(e)) self.stop() def stop(self): self.writer.stop() self.reader.stop() self.serial.close()
class EnclosureTemplate(object): """ This base class is intended to be used to interface with the hardware that is running Mycroft. It exposes all possible commands which can be sent to a Mycroft enclosure implementation. """ def __init__(self, bus=None, name=""): """ Args: bus: name: """ if bus is None: self.bus = WebsocketClient() self.event_thread = Thread(target=self._connect) self.event_thread.setDaemon(True) self.event_thread.start() else: self.bus = bus self.log = LOG self.name = name self.bus.on("open", self.on_ws_open) self.bus.on("enclosure.reset", self.reset) # enclosure commands for Mycroft's Hardware. self.bus.on("enclosure.system.reset", self.system_reset) self.bus.on("enclosure.system.mute", self.system_mute) self.bus.on("enclosure.system.unmute", self.system_unmute) self.bus.on("enclosure.system.blink", self.system_blink) # enclosure commands for eyes self.bus.on('enclosure.eyes.on', self.eyes_on) self.bus.on('enclosure.eyes.off', self.eyes_off) self.bus.on('enclosure.eyes.blink', self.eyes_blink) self.bus.on('enclosure.eyes.narrow', self.eyes_narrow) self.bus.on('enclosure.eyes.look', self.eyes_look) self.bus.on('enclosure.eyes.color', self.eyes_color) self.bus.on('enclosure.eyes.level', self.eyes_brightness) self.bus.on('enclosure.eyes.volume', self.eyes_volume) self.bus.on('enclosure.eyes.spin', self.eyes_spin) self.bus.on('enclosure.eyes.timedspin', self.eyes_timed_spin) self.bus.on('enclosure.eyes.reset', self.eyes_reset) self.bus.on('enclosure.eyes.setpixel', self.eyes_set_pixel) self.bus.on('enclosure.eyes.fill', self.eyes_fill) # enclosure commands for mouth self.bus.on("enclosure.mouth.reset", self.mouth_reset) self.bus.on("enclosure.mouth.talk", self.mouth_talk) self.bus.on("enclosure.mouth.think", self.mouth_think) self.bus.on("enclosure.mouth.listen", self.mouth_listen) self.bus.on("enclosure.mouth.smile", self.mouth_smile) self.bus.on("enclosure.mouth.viseme", self.mouth_viseme) self.bus.on("enclosure.mouth.text", self.mouth_text) self.bus.on("enclosure.mouth.display", self.mouth_display) self.bus.on("enclosure.mouth.events.activate", self.activate_mouth_events) self.bus.on("enclosure.mouth.events.deactivate", self.deactivate_mouth_events) # enclosure commands for weather display self.bus.on("enclosure.weather.display", self.weather_display) # enclosure commands for events self.bus.on("mycroft.awoken", self.handle_awake) self.bus.on("recognizer_loop:sleep", self.handle_sleep) self.bus.on("speak", self.handle_speak) self.bus.on('recognizer_loop:record_begin', self.record_begin) self.bus.on('recognizer_loop:record_end', self.record_end) self.bus.on('mycroft.audio.speech.start', self.talk_start) self.bus.on("enclosure.notify.no_internet", self.on_no_internet) self.activate_mouth_events() def shutdown(self): """ """ self.bus.remove("enclosure.reset", self.reset) self.bus.remove("enclosure.system.reset", self.system_reset) self.bus.remove("enclosure.system.mute", self.system_mute) self.bus.remove("enclosure.system.unmute", self.system_unmute) self.bus.remove("enclosure.system.blink", self.system_blink) self.bus.remove("enclosure.eyes.on", self.eyes_on) self.bus.remove("enclosure.eyes.off", self.eyes_off) self.bus.remove("enclosure.eyes.blink", self.eyes_blink) self.bus.remove("enclosure.eyes.narrow", self.eyes_narrow) self.bus.remove("enclosure.eyes.look", self.eyes_look) self.bus.remove("enclosure.eyes.color", self.eyes_color) self.bus.remove("enclosure.eyes.brightness", self.eyes_brightness) self.bus.remove("enclosure.eyes.reset", self.eyes_reset) self.bus.remove("enclosure.eyes.timedspin", self.eyes_timed_spin) self.bus.remove("enclosure.eyes.volume", self.eyes_volume) self.bus.remove("enclosure.eyes.spin", self.eyes_spin) self.bus.remove("enclosure.eyes.set_pixel", self.eyes_set_pixel) self.bus.remove("enclosure.mouth.reset", self.mouth_reset) self.bus.remove("enclosure.mouth.talk", self.mouth_talk) self.bus.remove("enclosure.mouth.think", self.mouth_think) self.bus.remove("enclosure.mouth.listen", self.mouth_listen) self.bus.remove("enclosure.mouth.smile", self.mouth_smile) self.bus.remove("enclosure.mouth.viseme", self.mouth_viseme) self.bus.remove("enclosure.mouth.text", self.mouth_text) self.bus.remove("enclosure.mouth.display", self.mouth_display) self.bus.remove("enclosure.mouth.events.activate", self.activate_mouth_events) self.bus.remove("enclosure.mouth.events.deactivate", self.deactivate_mouth_events) self.bus.remove("enclosure.weather.display", self.weather_display) self.bus.remove("mycroft.awoken", self.handle_awake) self.bus.remove("recognizer_loop:sleep", self.handle_sleep) self.bus.remove("speak", self.handle_speak) self.bus.remove('recognizer_loop:record_begin', self.record_begin) self.bus.remove('recognizer_loop:record_end', self.record_end) self.bus.remove('recognizer_loop:audio_output_start', self.talk_start) self.bus.remove("enclosure.notify.no_internet", self.on_no_internet) self.deactivate_mouth_events() def on_no_internet(self, message=None): """ Args: message: """ pass def on_ws_open(self): """ """ pass def speak(self, text): """ Args: text: """ self.bus.emit(Message("speak", {'utterance': text})) def run(self): ''' start enclosure ''' while True: time.sleep(1) def record_begin(self, message=None): ''' listening started ''' pass def record_end(self, message=None): ''' listening ended ''' pass def talk_start(self, message=None): ''' speaking started ''' pass def handle_awake(self, message=None): ''' handle wakeup animation ''' pass def handle_sleep(self, message=None): ''' handle naptime animation ''' # TODO naptime skill animation should be handled here pass def handle_speak(self, message=None): ''' handle speak messages, intended for enclosures that disregard visemes ''' pass def _connect(self): """ """ # Once the websocket has connected, just watch it for speak events self.bus.run_forever() def reset(self, message=None): """The enclosure should restore itself to a started state. Typically this would be represented by the eyes being 'open' and the mouth reset to its default (smile or blank). """ pass def system_reset(self, message=None): """The enclosure hardware should reset any CPUs, etc.""" pass def system_mute(self, message=None): """Mute (turn off) the system speaker.""" pass def system_unmute(self, message=None): """Unmute (turn on) the system speaker.""" pass def system_blink(self, message=None): """The 'eyes' should blink the given number of times. Args: times (int): number of times to blink """ pass def eyes_on(self, message=None): """Illuminate or show the eyes.""" pass def eyes_off(self, message=None): """Turn off or hide the eyes.""" pass def eyes_fill(self, message=None): pass def eyes_blink(self, message=None): """Make the eyes blink Args: side (str): 'r', 'l', or 'b' for 'right', 'left' or 'both' """ pass def eyes_narrow(self, message=None): """Make the eyes look narrow, like a squint""" pass def eyes_look(self, message=None): """Make the eyes look to the given side Args: side (str): 'r' for right 'l' for left 'u' for up 'd' for down 'c' for crossed """ pass def eyes_color(self, message=None): """Change the eye color to the given RGB color Args: r (int): 0-255, red value g (int): 0-255, green value b (int): 0-255, blue value """ pass def eyes_brightness(self, message=None): """Set the brightness of the eyes in the display. Args: level (int): 1-30, bigger numbers being brighter """ pass def eyes_reset(self, message=None): """Restore the eyes to their default (ready) state.""" pass def eyes_timed_spin(self, message=None): """Make the eyes 'roll' for the given time. Args: length (int): duration in milliseconds of roll, None = forever """ pass def eyes_volume(self, message=None): """Indicate the volume using the eyes Args: volume (int): 0 to 11 """ pass def eyes_spin(self, message=None): """ Args: """ pass def eyes_set_pixel(self, message=None): """ Args: """ pass def mouth_reset(self, message=None): """Restore the mouth display to normal (blank)""" pass def mouth_talk(self, message=None): """Show a generic 'talking' animation for non-synched speech""" pass def mouth_think(self, message=None): """Show a 'thinking' image or animation""" pass def mouth_listen(self, message=None): """Show a 'thinking' image or animation""" pass def mouth_smile(self, message=None): """Show a 'smile' image or animation""" pass def mouth_viseme(self, message=None): """Display a viseme mouth shape for synched speech Args: code (int): 0 = shape for sounds like 'y' or 'aa' 1 = shape for sounds like 'aw' 2 = shape for sounds like 'uh' or 'r' 3 = shape for sounds like 'th' or 'sh' 4 = neutral shape for no sound 5 = shape for sounds like 'f' or 'v' 6 = shape for sounds like 'oy' or 'ao' """ pass def mouth_text(self, message=None): """Display text (scrolling as needed) Args: text (str): text string to display """ pass def mouth_display(self, message=None): """Display images on faceplate. Currently supports images up to 16x8, or half the face. You can use the 'x' parameter to cover the other half of the faceplate. Args: img_code (str): text string that encodes a black and white image x (int): x offset for image y (int): y offset for image refresh (bool): specify whether to clear the faceplate before displaying the new image or not. Useful if you'd like to display muliple images on the faceplate at once. """ pass def weather_display(self, message=None): """Show a the temperature and a weather icon Args: img_code (char): one of the following icon codes 0 = sunny 1 = partly cloudy 2 = cloudy 3 = light rain 4 = raining 5 = stormy 6 = snowing 7 = wind/mist temp (int): the temperature (either C or F, not indicated) """ pass def activate_mouth_events(self, message=None): """Enable movement of the mouth with speech""" pass def deactivate_mouth_events(self, message=None): """Disable movement of the mouth with speech""" pass