Exemple #1
0
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()
Exemple #3
0
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()
Exemple #6
0
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"))
Exemple #7
0
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"))
Exemple #8
0
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
Exemple #11
0
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()
Exemple #12
0
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()
Exemple #13
0
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