def main():
    global ws
    global config
    ws = WebsocketClient()
    ConfigurationManager.init(ws)
    config = ConfigurationManager.get()
    speech.init(ws)

    # Setup control of pulse audio
    setup_pulseaudio_handlers(config.get('Audio').get('pulseaudio'))

    def echo(message):
        try:
            _message = json.loads(message)
            if 'mycroft.audio.service' not in _message.get('type'):
                return
            message = json.dumps(_message)
        except:
            pass
        LOG.debug(message)

    LOG.info("Staring Audio Services")
    ws.on('message', echo)
    ws.once('open', load_services_callback)
    try:
        ws.run_forever()
    except KeyboardInterrupt, e:
        LOG.exception(e)
        speech.shutdown()
        sys.exit()
Ejemplo n.º 2
0
def main():
    global ws
    lock = Lock('skills')  # prevent multiple instances of this service

    # Connect this Skill management process to the websocket
    ws = WebsocketClient()
    ConfigurationManager.init(ws)

    ignore_logs = ConfigurationManager.instance().get("ignore_logs")

    # Listen for messages and echo them for logging
    def _echo(message):
        try:
            _message = json.loads(message)

            if _message.get("type") in ignore_logs:
                return

            if _message.get("type") == "registration":
                # do not log tokens from registration messages
                _message["data"]["token"] = None
            message = json.dumps(_message)
        except:
            pass
        logger.debug(message)

    ws.on('message', _echo)

    # Kick off loading of skills
    ws.once('open', _load_skills)
    ws.run_forever()
Ejemplo n.º 3
0
class SkillContainer(object):
    def __init__(self, args):
        parser = argparse.ArgumentParser()
        parser.add_argument("--dependency-dir", default="./lib")
        parser.add_argument("--messagebus-host", default=messagebus_config.get("host"))
        parser.add_argument("--messagebus-port", type=int, default=messagebus_config.get("port"))
        parser.add_argument("--use-ssl", action='store_true', default=False)
        parser.add_argument("--enable-intent-skill", action='store_true', default=False)
        parser.add_argument("skill_directory", default=os.path.dirname(__file__))

        parsed_args = parser.parse_args(args)
        if os.path.exists(parsed_args.dependency_dir):
            sys.path.append(parsed_args.dependency_dir)

        self.skill_directory = parsed_args.skill_directory
        self.enable_intent_skill = parsed_args.enable_intent_skill

        self.client = WebsocketClient(host=parsed_args.messagebus_host,
                                      port=parsed_args.messagebus_port,
                                      ssl=parsed_args.use_ssl)

    def try_load_skill(self):
        if self.enable_intent_skill:
            intent_skill = create_intent_skill()
            intent_skill.bind(self.client)
            intent_skill.initialize()
        skill_descriptor = create_skill_descriptor(self.skill_directory)
        load_skill(skill_descriptor, self.client)

    def run(self):
        self.client.on('message', logger.debug)
        self.client.on('open', self.try_load_skill)
        self.client.on('error', logger.error)
        self.client.run_forever()
Ejemplo n.º 4
0
def main():
    global ws
    global config
    global pulse
    ws = WebsocketClient()
    ConfigurationManager.init(ws)
    config = ConfigurationManager.get()
    speech.init(ws)

    # Setup control of pulse audio
    if pulsectl and config.get('Audio').get('pulseaudio') == 'mute':
        pulse = pulsectl.Pulse('Mycroft-audio-service')
    else:
        pulse = None

    def echo(message):
        try:
            _message = json.loads(message)

            if 'mycroft.audio.service' in _message.get('type'):
                return
            message = json.dumps(_message)
        except:
            pass
        logger.debug(message)

    logger.info("Staring Audio Services")
    ws.on('message', echo)
    ws.once('open', load_services_callback)
    try:
        ws.run_forever()
    except KeyboardInterrupt, e:
        logger.exception(e)
        speech.shutdown()
        sys.exit()
Ejemplo n.º 5
0
def main():
    global ws
    global config
    ws = WebsocketClient()
    Configuration.init(ws)
    config = Configuration.get()
    speech.init(ws)

    # Setup control of pulse audio
    setup_pulseaudio_handlers(config.get('Audio').get('pulseaudio'))

    def echo(message):
        try:
            _message = json.loads(message)
            if 'mycroft.audio.service' not in _message.get('type'):
                return
            message = json.dumps(_message)
        except:
            pass
        LOG.debug(message)

    LOG.info("Staring Audio Services")
    ws.on('message', echo)
    ws.once('open', load_services_callback)
    try:
        ws.run_forever()
    except KeyboardInterrupt, e:
        LOG.exception(e)
        speech.shutdown()
        sys.exit()
Ejemplo n.º 6
0
def main():
    """ Main function. Run when file is invoked. """
    ws = WebsocketClient()
    Configuration.init(ws)
    speech.init(ws)

    def echo(message):
        """ Echo message bus messages. """
        try:
            _message = json.loads(message)
            if 'mycroft.audio.service' not in _message.get('type'):
                return
            message = json.dumps(_message)
        except Exception as e:
            LOG.exception(e)
        LOG.debug(message)

    LOG.info("Staring Audio Services")
    ws.on('message', echo)
    AudioService(ws)  # Connect audio service instance to message bus
    try:
        ws.run_forever()
    except KeyboardInterrupt as e:
        LOG.exception(e)
        speech.shutdown()
        sys.exit()
Ejemplo n.º 7
0
def main():
    global ws
    lock = Lock('skills')  # prevent multiple instances of this service

    # Connect this Skill management process to the websocket
    ws = WebsocketClient()
    ConfigurationManager.init(ws)

    ignore_logs = ConfigurationManager.instance().get("ignore_logs")

    # Listen for messages and echo them for logging
    def _echo(message):
        try:
            _message = json.loads(message)

            if _message.get("type") in ignore_logs:
                return

            if _message.get("type") == "registration":
                # do not log tokens from registration messages
                _message["data"]["token"] = None
            message = json.dumps(_message)
        except:
            pass
        logger.debug(message)

    ws.on('message', _echo)

    # Startup will be called after websocket is full live
    ws.once('open', _starting_up)
    ws.run_forever()
Ejemplo n.º 8
0
def main():
    global ws
    lock = Lock('skills')  # prevent multiple instances of this service

    # Connect this Skill management process to the websocket
    ws = WebsocketClient()
    ConfigurationManager.init(ws)

    ignore_logs = ConfigurationManager.instance().get("ignore_logs")

    # Listen for messages and echo them for logging
    def _echo(message):
        try:
            _message = json.loads(message)

            if _message.get("type") in ignore_logs:
                return

            if _message.get("type") == "registration":
                # do not log tokens from registration messages
                _message["data"]["token"] = None
            message = json.dumps(_message)
        except:
            pass
        LOG('SKILLS').debug(message)

    ws.on('message', _echo)
    ws.on('skill.converse.request', handle_converse_request)
    # Startup will be called after websocket is full live
    ws.once('open', _starting_up)
    ws.run_forever()
Ejemplo n.º 9
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.__init_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 __init_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)
        self.client.on('recognizer_loop:wakeword', self.eyes.blink)

    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()
Ejemplo n.º 10
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.__init_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 __init_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)
        self.client.on('recognizer_loop:wakeword', self.eyes.blink)

    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()
Ejemplo n.º 11
0
class SkillContainer(object):
    def __init__(self, args):
        params = self.__build_params(args)

        if params.config:
            ConfigurationManager.load_local([params.config])

        if exists(params.lib) and isdir(params.lib):
            sys.path.append(params.lib)

        sys.path.append(params.dir)
        self.dir = params.dir

        self.enable_intent_skill = params.enable_intent_skill

        self.__init_client(params)

    @staticmethod
    def __build_params(args):
        parser = argparse.ArgumentParser()
        parser.add_argument("--config", default="./mycroft.ini")
        parser.add_argument("--dir", default=dirname(__file__))
        parser.add_argument("--lib", default="./lib")
        parser.add_argument("--host", default=None)
        parser.add_argument("--port", default=None)
        parser.add_argument("--use-ssl", action='store_true', default=False)
        parser.add_argument("--enable-intent-skill",
                            action='store_true',
                            default=False)
        return parser.parse_args(args)

    def __init_client(self, params):
        config = ConfigurationManager.get().get("messagebus_client")

        if not params.host:
            params.host = config.get('host')
        if not params.port:
            params.port = config.get('port')

        self.client = WebsocketClient(host=params.host,
                                      port=params.port,
                                      ssl=params.use_ssl)

    def try_load_skill(self):
        if self.enable_intent_skill:
            intent_skill = create_intent_skill()
            intent_skill.bind(self.client)
            intent_skill.initialize()
        skill_descriptor = create_skill_descriptor(self.dir)
        load_skill(skill_descriptor, self.client)

    def run(self):
        self.client.on('message', logger.debug)
        self.client.on('open', self.try_load_skill)
        self.client.on('error', logger.error)
        self.client.run_forever()
Ejemplo n.º 12
0
class SkillContainer(object):
    def __init__(self, args):
        params = self.__build_params(args)

        if params.config:
            ConfigurationManager.load_local([params.config])

        if exists(params.lib) and isdir(params.lib):
            sys.path.append(params.lib)

        sys.path.append(params.dir)
        self.dir = params.dir

        self.enable_intent_skill = params.enable_intent_skill

        self.__init_client(params)

    @staticmethod
    def __build_params(args):
        parser = argparse.ArgumentParser()
        parser.add_argument("--config", default="./mycroft.ini")
        parser.add_argument("dir", nargs='?', default=dirname(__file__))
        parser.add_argument("--lib", default="./lib")
        parser.add_argument("--host", default=None)
        parser.add_argument("--port", default=None)
        parser.add_argument("--use-ssl", action='store_true', default=False)
        parser.add_argument("--enable-intent-skill", action='store_true',
                            default=False)
        return parser.parse_args(args)

    def __init_client(self, params):
        config = ConfigurationManager.get().get("messagebus_client")

        if not params.host:
            params.host = config.get('host')
        if not params.port:
            params.port = config.get('port')

        self.client = WebsocketClient(host=params.host,
                                      port=params.port,
                                      ssl=params.use_ssl)

    def try_load_skill(self):
        if self.enable_intent_skill:
            intent_skill = create_intent_skill()
            intent_skill.bind(self.client)
            intent_skill.initialize()
        skill_descriptor = create_skill_descriptor(self.dir)
        load_skill(skill_descriptor, self.client)

    def run(self):
        self.client.on('message', logger.debug)
        self.client.on('open', self.try_load_skill)
        self.client.on('error', logger.error)
        self.client.run_forever()
Ejemplo n.º 13
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)
Ejemplo n.º 14
0
class DevicePairingClient(object):
    def __init__(self, config=_config, pairing_code=None):
        self.config = config
        self.paired = False
        self.ws_client = WebsocketClient(host=config.get("host"),
                                         port=config.get("port"),
                                         path=config.get("route"),
                                         ssl=str2bool(config.get("ssl")))
        self.identity_manager = IdentityManager()
        self.identity = self.identity_manager.identity
        self.pairing_code = (pairing_code
                             if pairing_code else generate_pairing_code())

    def on_registration(self, message):
        # TODO: actually accept the configuration message and store it in
        # identity
        identity = self.identity_manager.get()
        register_payload = message.metadata
        if register_payload.get("device_id") == identity.device_id:
            identity.token = register_payload.get('token')
            identity.owner = register_payload.get('user')
            self.identity_manager.update(identity)
            self.ws_client.close()
            self.paired = True

    def send_device_info(self):
        msg = Message("device_info",
                      metadata={
                          "pairing_code": self.pairing_code,
                          "device_id": self.identity.device_id
                      })

        self.ws_client.emit(msg)

    @staticmethod
    def print_error(message):
        print(repr(message))

    def run(self):
        self.ws_client.on('registration', self.on_registration)
        self.ws_client.on('open', self.send_device_info)
        self.ws_client.on('error', self.print_error)
        self.ws_client.run_forever()
Ejemplo n.º 15
0
class DevicePairingClient(object):
    def __init__(self, config=_config, pairing_code=None):
        self.config = config
        self.paired = False
        self.ws_client = WebsocketClient(host=config.get("host"),
                                         port=config.get("port"),
                                         path=config.get("route"),
                                         ssl=str2bool(config.get("ssl")))
        self.identity_manager = IdentityManager()
        self.identity = self.identity_manager.identity
        self.pairing_code = (
            pairing_code if pairing_code else generate_pairing_code())

    def on_registration(self, message):
        # TODO: actually accept the configuration message and store it in
        # identity
        identity = self.identity_manager.get()
        register_payload = message.metadata
        if register_payload.get("device_id") == identity.device_id:
            identity.token = register_payload.get('token')
            identity.owner = register_payload.get('user')
            self.identity_manager.update(identity)
            self.ws_client.close()
            self.paired = True

    def send_device_info(self):
        msg = Message("device_info",
                      metadata={
                          "pairing_code": self.pairing_code,
                          "device_id": self.identity.device_id
                      })

        self.ws_client.emit(msg)

    @staticmethod
    def print_error(message):
        print(repr(message))

    def run(self):
        self.ws_client.on('registration', self.on_registration)
        self.ws_client.on('open', self.send_device_info)
        self.ws_client.on('error', self.print_error)
        self.ws_client.run_forever()
Ejemplo n.º 16
0
def main():
    global client
    client = WebsocketClient()

    def echo(message):
        try:
            _message = json.loads(message)

            if _message.get("message_type") == "registration":
                # do not log tokens from registration messages
                _message["metadata"]["token"] = None
            message = json.dumps(_message)
        except:
            pass
        logger.debug(message)

    client.on('message', echo)
    client.once('open', load_skills_callback)
    client.run_forever()
Ejemplo n.º 17
0
class SkillContainer(object):
    def __init__(self, args):
        parser = argparse.ArgumentParser()
        parser.add_argument("--dependency-dir", default="./lib")
        parser.add_argument("--messagebus-host",
                            default=messagebus_config.get("host"))
        parser.add_argument("--messagebus-port",
                            type=int,
                            default=messagebus_config.get("port"))
        parser.add_argument("--use-ssl", action='store_true', default=False)
        parser.add_argument("--enable-intent-skill",
                            action='store_true',
                            default=False)
        parser.add_argument("skill_directory",
                            default=os.path.dirname(__file__))

        parsed_args = parser.parse_args(args)
        if os.path.exists(parsed_args.dependency_dir):
            sys.path.append(parsed_args.dependency_dir)
        sys.path.append(parsed_args.skill_directory)

        self.skill_directory = parsed_args.skill_directory

        self.enable_intent_skill = parsed_args.enable_intent_skill

        self.client = WebsocketClient(host=parsed_args.messagebus_host,
                                      port=parsed_args.messagebus_port,
                                      ssl=parsed_args.use_ssl)

    def try_load_skill(self):
        if self.enable_intent_skill:
            intent_skill = create_intent_skill()
            intent_skill.bind(self.client)
            intent_skill.initialize()
        skill_descriptor = create_skill_descriptor(self.skill_directory)
        load_skill(skill_descriptor, self.client)

    def run(self):
        self.client.on('message', logger.debug)
        self.client.on('open', self.try_load_skill)
        self.client.on('error', logger.error)
        self.client.run_forever()
Ejemplo n.º 18
0
def main():
    global ws
    ws = WebsocketClient()
    ConfigurationManager.init(ws)

    def echo(message):
        try:
            _message = json.loads(message)

            if _message.get("type") == "registration":
                # do not log tokens from registration messages
                _message["data"]["token"] = None
            message = json.dumps(_message)
        except:
            pass
        logger.debug(message)

    ws.on('message', echo)
    ws.once('open', load_skills_callback)
    ws.run_forever()
Ejemplo n.º 19
0
def main():
    global ws
    ws = WebsocketClient()
    ConfigurationManager.init(ws)

    def echo(message):
        try:
            _message = json.loads(message)

            if _message.get("type") == "registration":
                # do not log tokens from registration messages
                _message["data"]["token"] = None
            message = json.dumps(_message)
        except:
            pass
        logger.debug(message)

    ws.on('message', echo)
    ws.once('open', load_skills_callback)
    ws.run_forever()
Ejemplo n.º 20
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()
Ejemplo n.º 21
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()
Ejemplo n.º 22
0
class WiFi:
    NAME = "WiFiClient"

    def __init__(self):
        self.iface = pyw.winterfaces()[0]
        self.ap = AccessPoint(self.iface)
        self.server = None
        self.client = WebsocketClient()
        self.enclosure = EnclosureAPI(self.client)
        self.config = ConfigurationManager.get().get(self.NAME)
        self.init_events()
        self.first_setup()

    def init_events(self):
        self.client.on('mycroft.wifi.start', self.start)
        self.client.on('mycroft.wifi.stop', self.stop)
        self.client.on('mycroft.wifi.scan', self.scan)
        self.client.on('mycroft.wifi.connect', self.connect)

    def first_setup(self):
        if str2bool(self.config.get('setup')):
            self.start()

    def start(self, event=None):
        LOG.info("Starting access point...")
        self.client.emit(
            Message(
                "speak",
                metadata={'utterance': "Initializing wireless setup mode."}))
        self.ap.up()
        if not self.server:
            self.server = WebServer(self.ap.ip, 80)
            self.server.start()
        self.enclosure.mouth_text(self.ap.password)
        LOG.info("Access point started!\n%s" % self.ap.__dict__)

    def scan(self, event=None):
        LOG.info("Scanning wifi connections...")
        networks = {}
        status = self.get_status()

        for cell in Cell.all(self.iface):
            update = True
            ssid = cell.ssid
            quality = self.get_quality(cell.quality)

            if networks.__contains__(ssid):
                update = networks.get(ssid).get("quality") < quality
            if update and ssid:
                networks[ssid] = {
                    'quality': quality,
                    'encrypted': cell.encrypted,
                    'connected': self.is_connected(ssid, status)
                }
        self.client.emit(
            Message("mycroft.wifi.scanned", {'networks': networks}))
        LOG.info("Wifi connections scanned!\n%s" % networks)

    @staticmethod
    def get_quality(quality):
        values = quality.split("/")
        return float(values[0]) / float(values[1])

    def connect(self, event=None):
        if event and event.metadata:
            ssid = event.metadata.get("ssid")
            connected = self.is_connected(ssid)

            if connected:
                LOG.warn("Mycroft is already connected to %s" % ssid)
            else:
                self.disconnect()
                LOG.info("Connecting to: %s" % ssid)
                nid = wpa(self.iface, 'add_network')
                wpa(self.iface, 'set_network', nid, 'ssid', '"' + ssid + '"')

                if event.metadata.__contains__("pass"):
                    psk = '"' + event.metadata.get("pass") + '"'
                    wpa(self.iface, 'set_network', nid, 'psk', psk)
                else:
                    wpa(self.iface, 'set_network', nid, 'key_mgmt', 'NONE')

                wpa(self.iface, 'enable', nid)
                connected = self.get_connected(ssid)
                if connected:
                    wpa(self.iface, 'save_config')
                    ConfigurationManager.set(self.NAME, 'setup', False, True)

            self.client.emit(
                Message("mycroft.wifi.connected", {'connected': connected}))
            LOG.info("Connection status for %s = %s" % (ssid, connected))

    def disconnect(self):
        status = self.get_status()
        nid = status.get("id")
        if nid:
            ssid = status.get("ssid")
            wpa(self.iface, 'disable', nid)
            LOG.info("Disconnecting %s id: %s" % (ssid, nid))

    def get_status(self):
        res = cli('wpa_cli', '-i', self.iface, 'status')
        out = str(res.get("stdout"))
        if out:
            return dict(o.split("=") for o in out.split("\n")[:-1])
        return {}

    def get_connected(self, ssid, retry=5):
        connected = self.is_connected(ssid)
        while not connected and retry > 0:
            sleep(2)
            retry -= 1
            connected = self.is_connected(ssid)
        return connected

    def is_connected(self, ssid, status=None):
        status = status or self.get_status()
        state = status.get("wpa_state")
        return status.get("ssid") == ssid and state == "COMPLETED"

    def stop(self, event=None):
        LOG.info("Stopping access point...")
        self.ap.down()
        if self.server:
            self.server.server.shutdown()
            self.server.server.server_close()
            self.server.join()
            self.server = None
        LOG.info("Access point stopped!")

    def run(self):
        try:
            self.client.run_forever()
        except Exception as e:
            LOG.error("Error: {0}".format(e))
            self.stop()
Ejemplo n.º 23
0
class Enclosure:
    def __init__(self):
        # Establish Enclosure's websocket connection to the messagebus
        self.bus = WebsocketClient()

        # Load full config
        Configuration.init(self.bus)
        config = Configuration.get()

        self.lang = config['lang']
        self.config = config.get("enclosure")
        self.global_config = config

        # This datastore holds the data associated with the GUI provider. Data
        # is stored in Namespaces, so you can have:
        # self.datastore["namespace"]["name"] = value
        # Typically the namespace is a meaningless identifier, but there is a
        # special "SYSTEM" namespace.
        self.datastore = {}

        # self.loaded is a list, each element consists of a namespace named
        # tuple.
        # The namespace namedtuple has the properties "name" and "pages"
        # The name contains the namespace name as a string and pages is a
        # mutable list of loaded pages.
        #
        # [Namespace name, [List of loaded qml pages]]
        # [
        # ["SKILL_NAME", ["page1.qml, "page2.qml", ... , "pageN.qml"]
        # [...]
        # ]
        self.loaded = []  # list of lists in order.
        self.explicit_move = True  # Set to true to send reorder commands

        # Listen for new GUI clients to announce themselves on the main bus
        self.GUIs = {}      # GUIs, either local or remote
        self.active_namespaces = []
        self.bus.on("mycroft.gui.connected", self.on_gui_client_connected)
        self.register_gui_handlers()

        # First send any data:
        self.bus.on("gui.value.set", self.on_gui_set_value)
        self.bus.on("gui.page.show", self.on_gui_show_page)
        self.bus.on("gui.page.delete", self.on_gui_delete_page)
        self.bus.on("gui.clear.namespace", self.on_gui_delete_namespace)
        self.bus.on("gui.event.send", self.on_gui_send_event)

    def run(self):
        try:
            self.bus.run_forever()
        except Exception as e:
            LOG.error("Error: {0}".format(e))
            self.stop()

    ######################################################################
    # GUI client API

    def send(self, *args, **kwargs):
        """ Send to all registered GUIs. """
        for gui in self.GUIs.values():
            if gui.socket:
                gui.socket.send(*args, **kwargs)
            else:
                LOG.error('GUI connection {} has no socket!'.format(gui))

    def on_gui_send_event(self, message):
        """ Send an event to the GUIs. """
        try:
            data = {'type': 'mycroft.events.triggered',
                    'namespace': message.data.get('__from'),
                    'event_name': message.data.get('event_name'),
                    'params': message.data.get('params')}
            self.send(data)
        except Exception as e:
            LOG.error('Could not send event ({})'.format(repr(e)))

    def on_gui_set_value(self, message):
        data = message.data
        namespace = data.get("__from", "")

        # Pass these values on to the GUI renderers
        for key in data:
            if key not in RESERVED_KEYS:
                try:
                    self.set(namespace, key, data[key])
                except Exception as e:
                    LOG.exception(repr(e))

    def set(self, namespace, name, value):
        """ Perform the send of the values to the connected GUIs. """
        if namespace not in self.datastore:
            self.datastore[namespace] = {}
        if self.datastore[namespace].get(name) != value:
            self.datastore[namespace][name] = value

            # If the namespace is loaded send data to GUI
            if namespace in [l.name for l in self.loaded]:
                msg = {"type": "mycroft.session.set",
                       "namespace": namespace,
                       "data": {name: value}}
                self.send(msg)

    def on_gui_delete_page(self, message):
        """ Bus handler for removing pages. """
        page, namespace, _ = _get_page_data(message)
        try:
            self.remove_pages(namespace, page)
        except Exception as e:
            LOG.exception(repr(e))

    def on_gui_delete_namespace(self, message):
        """ Bus handler for removing namespace. """
        try:
            namespace = message.data['__from']
            self.remove_namespace(namespace)
        except Exception as e:
            LOG.exception(repr(e))

    def on_gui_show_page(self, message):
        try:
            page, namespace, index = _get_page_data(message)
            # Pass the request to the GUI(s) to pull up a page template
            self.show(namespace, page, index)
        except Exception as e:
            LOG.exception(repr(e))

    def __find_namespace(self, namespace):
        for i, skill in enumerate(self.loaded):
            if skill[0] == namespace:
                return i
        return None

    def __insert_pages(self, namespace, pages):
        """ Insert pages into the namespace

        Args:
            namespace (str): Namespace to add to
            pages (list):    Pages (str) to insert
        """
        LOG.debug("Inserting new pages")
        if not isinstance(pages, list):
            raise ValueError('Argument must be list of pages')

        self.send({"type": "mycroft.gui.list.insert",
                   "namespace": namespace,
                   "position": len(self.loaded[0].pages),
                   "data": [{"url": p} for p in pages]
                   })
        # Insert the pages into local reprensentation as well.
        self.loaded[0].pages += pages

    def __remove_page(self, namespace, pos):
        """ Delete page.

        Args:
            namespace (str): Namespace to remove from
            pos (int):      Page position to remove
        """
        LOG.debug("Deleting {} from {}".format(pos, namespace))
        self.send({"type": "mycroft.gui.list.remove",
                   "namespace": namespace,
                   "position": pos,
                   "items_number": 1
                   })
        # Remove the page from the local reprensentation as well.
        self.loaded[0].pages.pop(pos)

    def __insert_new_namespace(self, namespace, pages):
        """ Insert new namespace and pages.

        This first sends a message adding a new namespace at the
        highest priority (position 0 in the namespace stack)

        Args:
            namespace (str):  The skill namespace to create
            pages (str):      Pages to insert (name matches QML)
        """
        LOG.debug("Inserting new namespace")
        self.send({"type": "mycroft.session.list.insert",
                   "namespace": "mycroft.system.active_skills",
                   "position": 0,
                   "data": [{"skill_id": namespace}]
                   })

        # Load any already stored Data
        data = self.datastore.get(namespace, {})
        for key in data:
            msg = {"type": "mycroft.session.set",
                   "namespace": namespace,
                   "data": {key: data[key]}}
            self.send(msg)

        LOG.debug("Inserting new page")
        self.send({"type": "mycroft.gui.list.insert",
                   "namespace": namespace,
                   "position": 0,
                   "data": [{"url": p} for p in pages]
                   })
        # Make sure the local copy is updated
        self.loaded.insert(0, Namespace(namespace, pages))

    def __move_namespace(self, from_pos, to_pos):
        """ Move an existing namespace to a new position in the stack.

        Args:
            from_pos (int): Position in the stack to move from
            to_pos (int): Position to move to
        """
        LOG.debug("Activating existing namespace")
        # Seems like the namespace is moved to the top automatically when
        # a page change is done. Deactivating this for now.
        if self.explicit_move:
            LOG.debug("move {} to {}".format(from_pos, to_pos))
            self.send({"type": "mycroft.session.list.move",
                       "namespace": "mycroft.system.active_skills",
                       "from": from_pos, "to": to_pos,
                       "items_number": 1})
        # Move the local representation of the skill from current
        # position to position 0.
        self.loaded.insert(to_pos, self.loaded.pop(from_pos))

    def __switch_page(self, namespace, pages):
        """ Switch page to an already loaded page.

        Args:
            pages (list): pages (str) to switch to
            namespace (str):  skill namespace
        """
        try:
            num = self.loaded[0].pages.index(pages[0])
        except Exception as e:
            LOG.exception(repr(e))
            num = 0

        LOG.debug('Switching to already loaded page at '
                  'index {} in namespace {}'.format(num, namespace))
        self.send({"type": "mycroft.events.triggered",
                   "namespace": namespace,
                   "event_name": "page_gained_focus",
                   "data": {"number": num}})

    def show(self, namespace, page, index):
        """ Show a page and load it as needed.

        Args:
            page (str or list): page(s) to show
            namespace (str):  skill namespace
            index (int): ??? TODO: Unused in code ???

        TODO: - Update sync to match.
              - Separate into multiple functions/methods
        """

        LOG.debug("GUIConnection activating: " + namespace)
        pages = page if isinstance(page, list) else [page]

        # find namespace among loaded namespaces
        try:
            index = self.__find_namespace(namespace)
            if index is None:
                # This namespace doesn't exist, insert them first so they're
                # shown.
                self.__insert_new_namespace(namespace, pages)
                return
            else:  # Namespace exists
                if index > 0:
                    # Namespace is inactive, activate it by moving it to
                    # position 0
                    self.__move_namespace(index, 0)

                # Find if any new pages needs to be inserted
                new_pages = [p for p in pages if p not in self.loaded[0].pages]
                if new_pages:
                    self.__insert_pages(namespace, new_pages)
                else:
                    # No new pages, just switch
                    self.__switch_page(namespace, pages)
        except Exception as e:
            LOG.exception(repr(e))

    def remove_namespace(self, namespace):
        """ Remove namespace.

        Args:
            namespace (str): namespace to remove
        """
        index = self.__find_namespace(namespace)
        if index is None:
            return
        else:
            LOG.debug("Removing namespace {} at {}".format(namespace, index))
            self.send({"type": "mycroft.session.list.remove",
                       "namespace": "mycroft.system.active_skills",
                       "position": index,
                       "items_number": 1
                       })
            # Remove namespace from loaded namespaces
            self.loaded.pop(index)

    def remove_pages(self, namespace, pages):
        """ Remove the listed pages from the provided namespace.

        Args:
            namespace (str):    The namespace to modify
            pages (list):       List of page names (str) to delete
        """
        try:
            index = self.__find_namespace(namespace)
            if index is None:
                return
            else:
                # Remove any pages that doesn't exist in the namespace
                pages = [p for p in pages if p in self.loaded[index].pages]
                # Make sure to remove pages from the back
                indexes = [self.loaded[index].pages.index(p) for p in pages]
                indexes = sorted(indexes)
                indexes.reverse()
                for page_index in indexes:
                    self.__remove_page(namespace, page_index)
        except Exception as e:
            LOG.exception(repr(e))

    ######################################################################
    # GUI client socket
    #
    # The basic mechanism is:
    # 1) GUI client announces itself on the main messagebus
    # 2) Mycroft prepares a port for a socket connection to this GUI
    # 3) The port is announced over the messagebus
    # 4) The GUI connects on the socket
    # 5) Connection persists for graphical interaction indefinitely
    #
    # If the connection is lost, it must be renegotiated and restarted.
    def on_gui_client_connected(self, message):
        # GUI has announced presence
        LOG.debug("on_gui_client_connected")
        gui_id = message.data.get("gui_id")

        # Spin up a new communication socket for this GUI
        if gui_id in self.GUIs:
            # TODO: Close it?
            pass
        self.GUIs[gui_id] = GUIConnection(gui_id, self.global_config,
                                          self.callback_disconnect, self)
        LOG.debug("Heard announcement from gui_id: {}".format(gui_id))

        # Announce connection, the GUI should connect on it soon
        self.bus.emit(Message("mycroft.gui.port",
                              {"port": self.GUIs[gui_id].port,
                               "gui_id": gui_id}))

    def callback_disconnect(self, gui_id):
        LOG.info("Disconnecting!")
        # TODO: Whatever is needed to kill the websocket instance
        LOG.info(self.GUIs.keys())
        LOG.info('deleting: {}'.format(gui_id))
        if gui_id in self.GUIs:
            del self.GUIs[gui_id]
        else:
            LOG.warning('ID doesn\'t exist')

    def register_gui_handlers(self):
        # TODO: Register handlers for standard (Mark 1) events
        # self.bus.on('enclosure.eyes.on', self.on)
        # self.bus.on('enclosure.eyes.off', self.off)
        # self.bus.on('enclosure.eyes.blink', self.blink)
        # self.bus.on('enclosure.eyes.narrow', self.narrow)
        # self.bus.on('enclosure.eyes.look', self.look)
        # self.bus.on('enclosure.eyes.color', self.color)
        # self.bus.on('enclosure.eyes.level', self.brightness)
        # self.bus.on('enclosure.eyes.volume', self.volume)
        # self.bus.on('enclosure.eyes.spin', self.spin)
        # self.bus.on('enclosure.eyes.timedspin', self.timed_spin)
        # self.bus.on('enclosure.eyes.reset', self.reset)
        # self.bus.on('enclosure.eyes.setpixel', self.set_pixel)
        # self.bus.on('enclosure.eyes.fill', self.fill)

        # self.bus.on('enclosure.mouth.reset', self.reset)
        # self.bus.on('enclosure.mouth.talk', self.talk)
        # self.bus.on('enclosure.mouth.think', self.think)
        # self.bus.on('enclosure.mouth.listen', self.listen)
        # self.bus.on('enclosure.mouth.smile', self.smile)
        # self.bus.on('enclosure.mouth.viseme', self.viseme)
        # self.bus.on('enclosure.mouth.text', self.text)
        # self.bus.on('enclosure.mouth.display', self.display)
        # self.bus.on('enclosure.mouth.display_image', self.display_image)
        # self.bus.on('enclosure.weather.display', self.display_weather)

        # self.bus.on('recognizer_loop:record_begin', self.mouth.listen)
        # self.bus.on('recognizer_loop:record_end', self.mouth.reset)
        # self.bus.on('recognizer_loop:audio_output_start', self.mouth.talk)
        # self.bus.on('recognizer_loop:audio_output_end', self.mouth.reset)
        pass
Ejemplo n.º 24
0
class Enclosure():
    def __init__(self):
        # Establish Enclosure's websocket connection to the messagebus
        self.bus = WebsocketClient()

        # Load full config
        Configuration.init(self.bus)
        config = Configuration.get()

        self.lang = config['lang']
        self.config = config.get("enclosure")
        self.global_config = config

        # Listen for new GUI clients to announce themselves on the main bus
        self.GUIs = {}  # GUIs, either local or remote
        self.active_namespaces = []
        self.bus.on("mycroft.gui.connected", self.on_gui_client_connected)
        self.register_gui_handlers()

        # First send any data:
        self.bus.on("gui.value.set", self.on_gui_set_value)
        self.bus.on("gui.page.show", self.on_gui_show_page)

    def run(self):
        try:
            self.bus.run_forever()
        except Exception as e:
            LOG.error("Error: {0}".format(e))
            self.stop()

    ######################################################################
    # GUI client API

    def _gui_activate(self, namespace, move_to_top=False):
        if not namespace:
            return

        if namespace not in self.active_namespaces:
            if move_to_top:
                self.active_namespaces.insert(0, namespace)
            else:
                self.active_namespaces.append(namespace)
        elif move_to_top:
            self.active_namespaces.remove(namespace)
            self.active_namespaces.insert(0, namespace)
        # TODO: Keep a timestamp and auto-cull?

    def on_gui_set_value(self, message):
        data = message.data
        namespace = data.get("__from", "")

        self._gui_activate(namespace)

        # Pass these values on to the GUI renderers
        for id in self.GUIs:
            for key in data:
                if key != "__from":
                    self.GUIs[id].set(namespace, key, data[key])

    def on_gui_show_page(self, message):
        data = message.data

        # Note:  'page' can be either a string or a list of strings
        if 'page' not in data:
            return
        if 'index' in data:
            index = data['index']
        else:
            index = 0
        namespace = data.get("__from", "")
        self._gui_activate(namespace, move_to_top=True)

        # Pass the request to the GUI(s) to pull up a page template
        for id in self.GUIs:
            self.GUIs[id].show(namespace, data['page'], index)

    ######################################################################
    # GUI client socket
    #
    # The basic mechanism is:
    # 1) GUI client announces itself on the main messagebus
    # 2) Mycroft prepares a port for a socket connection to this GUI
    # 3) The port is announced over the messagebus
    # 4) The GUI connects on the socket
    # 5) Connection persists for graphical interaction indefinitely
    #
    # If the connection is lost, it must be renegotiated and restarted.

    def on_gui_client_connected(self, message):
        # GUI has announced presence
        DEBUG("on_gui_client_connected")
        gui_id = message.data.get("gui_id")

        # Spin up a new communication socket for this GUI
        if gui_id in self.GUIs:
            # TODO: Close it?
            pass
        self.GUIs[gui_id] = GUIConnection(gui_id, self.global_config,
                                          self.callback_disconnect, self)
        DEBUG("Heard announcement from gui_id: {}".format(gui_id))

        # Announce connection, the GUI should connect on it soon
        self.bus.emit(
            Message("mycroft.gui.port", {
                "port": self.GUIs[gui_id].port,
                "gui_id": gui_id
            }))

    def callback_disconnect(self, gui_id):
        DEBUG("Disconnecting!")
        # TODO: Whatever is needed to kill the websocket instance
        del self.GUIs[gui_id]

    def register_gui_handlers(self):
        # TODO: Register handlers for standard (Mark 1) events
        # self.bus.on('enclosure.eyes.on', self.on)
        # self.bus.on('enclosure.eyes.off', self.off)
        # self.bus.on('enclosure.eyes.blink', self.blink)
        # self.bus.on('enclosure.eyes.narrow', self.narrow)
        # self.bus.on('enclosure.eyes.look', self.look)
        # self.bus.on('enclosure.eyes.color', self.color)
        # self.bus.on('enclosure.eyes.level', self.brightness)
        # self.bus.on('enclosure.eyes.volume', self.volume)
        # self.bus.on('enclosure.eyes.spin', self.spin)
        # self.bus.on('enclosure.eyes.timedspin', self.timed_spin)
        # self.bus.on('enclosure.eyes.reset', self.reset)
        # self.bus.on('enclosure.eyes.setpixel', self.set_pixel)
        # self.bus.on('enclosure.eyes.fill', self.fill)

        # self.bus.on('enclosure.mouth.reset', self.reset)
        # self.bus.on('enclosure.mouth.talk', self.talk)
        # self.bus.on('enclosure.mouth.think', self.think)
        # self.bus.on('enclosure.mouth.listen', self.listen)
        # self.bus.on('enclosure.mouth.smile', self.smile)
        # self.bus.on('enclosure.mouth.viseme', self.viseme)
        # self.bus.on('enclosure.mouth.text', self.text)
        # self.bus.on('enclosure.mouth.display', self.display)
        # self.bus.on('enclosure.mouth.display_image', self.display_image)
        # self.bus.on('enclosure.weather.display', self.display_weather)

        # self.bus.on('recognizer_loop:record_begin', self.mouth.listen)
        # self.bus.on('recognizer_loop:record_end', self.mouth.reset)
        # self.bus.on('recognizer_loop:audio_output_start', self.mouth.talk)
        # self.bus.on('recognizer_loop:audio_output_end', self.mouth.reset)
        pass
Ejemplo n.º 25
0
from mycroft.messagebus.message import Message

if len(sys.argv) == 2:
    messageToSend = sys.argv[1]
elif len(sys.argv) > 2:
    messageToSend = " ".join(sys.argv[2:])
else:
    filename = os.path.basename(__file__)
    print filename
    print "Simple command line interface to the messagebus."
    print "Usage:   messagebus_emit <utterance>\n"
    print "         where <utterance> is treated as if spoken to Mycroft."
    print "Example: " + filename + " mycroft.wifi.start"
    exit()


def onConnected(event=None):
    print "Sending message...'" + messageToSend + "'"
    messagebusClient.emit(Message(messageToSend))
    messagebusClient.close()
    exit()


# Establish a connection with the messagebus
messagebusClient = WebsocketClient()
messagebusClient.on('connected', onConnected)


# This will block until the client gets closed
messagebusClient.run_forever()
Ejemplo n.º 26
0
class Enclosure:
    def __init__(self):
        # Establish Enclosure's websocket connection to the messagebus
        self.bus = WebsocketClient()

        # Load full config
        Configuration.init(self.bus)
        config = Configuration.get()

        self.lang = config['lang']
        self.config = config.get("enclosure")
        self.global_config = config

        # This datastore holds the data associated with the GUI provider. Data
        # is stored in Namespaces, so you can have:
        # self.datastore["namespace"]["name"] = value
        # Typically the namespace is a meaningless identifier, but there is a
        # special "SYSTEM" namespace.
        self.datastore = {}

        # self.loaded is a list, each element consists of a namespace named
        # tuple.
        # The namespace namedtuple has the properties "name" and "pages"
        # The name contains the namespace name as a string and pages is a
        # mutable list of loaded pages.
        #
        # [Namespace name, [List of loaded qml pages]]
        # [
        # ["SKILL_NAME", ["page1.qml, "page2.qml", ... , "pageN.qml"]
        # [...]
        # ]
        self.loaded = []  # list of lists in order.
        self.explicit_move = True  # Set to true to send reorder commands

        # Listen for new GUI clients to announce themselves on the main bus
        self.GUIs = {}  # GUIs, either local or remote
        self.active_namespaces = []
        self.bus.on("mycroft.gui.connected", self.on_gui_client_connected)
        self.register_gui_handlers()

        # First send any data:
        self.bus.on("gui.value.set", self.on_gui_set_value)
        self.bus.on("gui.page.show", self.on_gui_show_page)
        self.bus.on("gui.page.delete", self.on_gui_delete_page)
        self.bus.on("gui.clear.namespace", self.on_gui_delete_namespace)
        self.bus.on("gui.event.send", self.on_gui_send_event)

    def run(self):
        try:
            self.bus.run_forever()
        except Exception as e:
            LOG.error("Error: {0}".format(e))
            self.stop()

    ######################################################################
    # GUI client API

    def send(self, *args, **kwargs):
        """ Send to all registered GUIs. """
        for gui in self.GUIs.values():
            if gui.socket:
                gui.socket.send(*args, **kwargs)
            else:
                LOG.error('GUI connection {} has no socket!'.format(gui))

    def on_gui_send_event(self, message):
        """ Send an event to the GUIs. """
        try:
            data = {
                'type': 'mycroft.events.triggered',
                'namespace': message.data.get('__from'),
                'event_name': message.data.get('event_name'),
                'params': message.data.get('params')
            }
            self.send(data)
        except Exception as e:
            LOG.error('Could not send event ({})'.format(repr(e)))

    def on_gui_set_value(self, message):
        data = message.data
        namespace = data.get("__from", "")

        # Pass these values on to the GUI renderers
        for key in data:
            if key not in RESERVED_KEYS:
                try:
                    self.set(namespace, key, data[key])
                except Exception as e:
                    LOG.exception(repr(e))

    def set(self, namespace, name, value):
        """ Perform the send of the values to the connected GUIs. """
        if namespace not in self.datastore:
            self.datastore[namespace] = {}
        if self.datastore[namespace].get(name) != value:
            self.datastore[namespace][name] = value

            # If the namespace is loaded send data to GUI
            if namespace in [l.name for l in self.loaded]:
                msg = {
                    "type": "mycroft.session.set",
                    "namespace": namespace,
                    "data": {
                        name: value
                    }
                }
                self.send(msg)

    def on_gui_delete_page(self, message):
        """ Bus handler for removing pages. """
        page, namespace, _ = _get_page_data(message)
        try:
            with namespace_lock:
                self.remove_pages(namespace, page)
        except Exception as e:
            LOG.exception(repr(e))

    def on_gui_delete_namespace(self, message):
        """ Bus handler for removing namespace. """
        try:
            namespace = message.data['__from']
            with namespace_lock:
                self.remove_namespace(namespace)
        except Exception as e:
            LOG.exception(repr(e))

    def on_gui_show_page(self, message):
        try:
            page, namespace, index = _get_page_data(message)
            # Pass the request to the GUI(s) to pull up a page template
            with namespace_lock:
                self.show(namespace, page, index)
        except Exception as e:
            LOG.exception(repr(e))

    def __find_namespace(self, namespace):
        for i, skill in enumerate(self.loaded):
            if skill[0] == namespace:
                return i
        return None

    def __insert_pages(self, namespace, pages):
        """ Insert pages into the namespace

        Args:
            namespace (str): Namespace to add to
            pages (list):    Pages (str) to insert
        """
        LOG.debug("Inserting new pages")
        if not isinstance(pages, list):
            raise ValueError('Argument must be list of pages')

        self.send({
            "type": "mycroft.gui.list.insert",
            "namespace": namespace,
            "position": len(self.loaded[0].pages),
            "data": [{
                "url": p
            } for p in pages]
        })
        # Insert the pages into local reprensentation as well.
        updated = Namespace(self.loaded[0].name, self.loaded[0].pages + pages)
        self.loaded[0] = updated

    def __remove_page(self, namespace, pos):
        """ Delete page.

        Args:
            namespace (str): Namespace to remove from
            pos (int):      Page position to remove
        """
        LOG.debug("Deleting {} from {}".format(pos, namespace))
        self.send({
            "type": "mycroft.gui.list.remove",
            "namespace": namespace,
            "position": pos,
            "items_number": 1
        })
        # Remove the page from the local reprensentation as well.
        self.loaded[0].pages.pop(pos)

    def __insert_new_namespace(self, namespace, pages):
        """ Insert new namespace and pages.

        This first sends a message adding a new namespace at the
        highest priority (position 0 in the namespace stack)

        Args:
            namespace (str):  The skill namespace to create
            pages (str):      Pages to insert (name matches QML)
        """
        LOG.debug("Inserting new namespace")
        self.send({
            "type": "mycroft.session.list.insert",
            "namespace": "mycroft.system.active_skills",
            "position": 0,
            "data": [{
                "skill_id": namespace
            }]
        })

        # Load any already stored Data
        data = self.datastore.get(namespace, {})
        for key in data:
            msg = {
                "type": "mycroft.session.set",
                "namespace": namespace,
                "data": {
                    key: data[key]
                }
            }
            self.send(msg)

        LOG.debug("Inserting new page")
        self.send({
            "type": "mycroft.gui.list.insert",
            "namespace": namespace,
            "position": 0,
            "data": [{
                "url": p
            } for p in pages]
        })
        # Make sure the local copy is updated
        self.loaded.insert(0, Namespace(namespace, pages))

    def __move_namespace(self, from_pos, to_pos):
        """ Move an existing namespace to a new position in the stack.

        Args:
            from_pos (int): Position in the stack to move from
            to_pos (int): Position to move to
        """
        LOG.debug("Activating existing namespace")
        # Seems like the namespace is moved to the top automatically when
        # a page change is done. Deactivating this for now.
        if self.explicit_move:
            LOG.debug("move {} to {}".format(from_pos, to_pos))
            self.send({
                "type": "mycroft.session.list.move",
                "namespace": "mycroft.system.active_skills",
                "from": from_pos,
                "to": to_pos,
                "items_number": 1
            })
        # Move the local representation of the skill from current
        # position to position 0.
        self.loaded.insert(to_pos, self.loaded.pop(from_pos))

    def __switch_page(self, namespace, pages):
        """ Switch page to an already loaded page.

        Args:
            pages (list): pages (str) to switch to
            namespace (str):  skill namespace
        """
        try:
            num = self.loaded[0].pages.index(pages[0])
        except Exception as e:
            LOG.exception(repr(e))
            num = 0

        LOG.debug('Switching to already loaded page at '
                  'index {} in namespace {}'.format(num, namespace))
        self.send({
            "type": "mycroft.events.triggered",
            "namespace": namespace,
            "event_name": "page_gained_focus",
            "data": {
                "number": num
            }
        })

    def show(self, namespace, page, index):
        """ Show a page and load it as needed.

        Args:
            page (str or list): page(s) to show
            namespace (str):  skill namespace
            index (int): ??? TODO: Unused in code ???

        TODO: - Update sync to match.
              - Separate into multiple functions/methods
        """

        LOG.debug("GUIConnection activating: " + namespace)
        pages = page if isinstance(page, list) else [page]

        # find namespace among loaded namespaces
        try:
            index = self.__find_namespace(namespace)
            if index is None:
                # This namespace doesn't exist, insert them first so they're
                # shown.
                self.__insert_new_namespace(namespace, pages)
                return
            else:  # Namespace exists
                if index > 0:
                    # Namespace is inactive, activate it by moving it to
                    # position 0
                    self.__move_namespace(index, 0)

                # Find if any new pages needs to be inserted
                new_pages = [p for p in pages if p not in self.loaded[0].pages]
                if new_pages:
                    self.__insert_pages(namespace, new_pages)
                else:
                    # No new pages, just switch
                    self.__switch_page(namespace, pages)
        except Exception as e:
            LOG.exception(repr(e))

    def remove_namespace(self, namespace):
        """ Remove namespace.

        Args:
            namespace (str): namespace to remove
        """
        index = self.__find_namespace(namespace)
        if index is None:
            return
        else:
            LOG.debug("Removing namespace {} at {}".format(namespace, index))
            self.send({
                "type": "mycroft.session.list.remove",
                "namespace": "mycroft.system.active_skills",
                "position": index,
                "items_number": 1
            })
            # Remove namespace from loaded namespaces
            self.loaded.pop(index)

    def remove_pages(self, namespace, pages):
        """ Remove the listed pages from the provided namespace.

        Args:
            namespace (str):    The namespace to modify
            pages (list):       List of page names (str) to delete
        """
        try:
            index = self.__find_namespace(namespace)
            if index is None:
                return
            else:
                # Remove any pages that doesn't exist in the namespace
                pages = [p for p in pages if p in self.loaded[index].pages]
                # Make sure to remove pages from the back
                indexes = [self.loaded[index].pages.index(p) for p in pages]
                indexes = sorted(indexes)
                indexes.reverse()
                for page_index in indexes:
                    self.__remove_page(namespace, page_index)
        except Exception as e:
            LOG.exception(repr(e))

    ######################################################################
    # GUI client socket
    #
    # The basic mechanism is:
    # 1) GUI client announces itself on the main messagebus
    # 2) Mycroft prepares a port for a socket connection to this GUI
    # 3) The port is announced over the messagebus
    # 4) The GUI connects on the socket
    # 5) Connection persists for graphical interaction indefinitely
    #
    # If the connection is lost, it must be renegotiated and restarted.
    def on_gui_client_connected(self, message):
        # GUI has announced presence
        LOG.debug("on_gui_client_connected")
        gui_id = message.data.get("gui_id")

        # Spin up a new communication socket for this GUI
        if gui_id in self.GUIs:
            # TODO: Close it?
            pass
        self.GUIs[gui_id] = GUIConnection(gui_id, self.global_config,
                                          self.callback_disconnect, self)
        LOG.debug("Heard announcement from gui_id: {}".format(gui_id))

        # Announce connection, the GUI should connect on it soon
        self.bus.emit(
            Message("mycroft.gui.port", {
                "port": self.GUIs[gui_id].port,
                "gui_id": gui_id
            }))

    def callback_disconnect(self, gui_id):
        LOG.info("Disconnecting!")
        # TODO: Whatever is needed to kill the websocket instance
        LOG.info(self.GUIs.keys())
        LOG.info('deleting: {}'.format(gui_id))
        if gui_id in self.GUIs:
            del self.GUIs[gui_id]
        else:
            LOG.warning('ID doesn\'t exist')

    def register_gui_handlers(self):
        # TODO: Register handlers for standard (Mark 1) events
        # self.bus.on('enclosure.eyes.on', self.on)
        # self.bus.on('enclosure.eyes.off', self.off)
        # self.bus.on('enclosure.eyes.blink', self.blink)
        # self.bus.on('enclosure.eyes.narrow', self.narrow)
        # self.bus.on('enclosure.eyes.look', self.look)
        # self.bus.on('enclosure.eyes.color', self.color)
        # self.bus.on('enclosure.eyes.level', self.brightness)
        # self.bus.on('enclosure.eyes.volume', self.volume)
        # self.bus.on('enclosure.eyes.spin', self.spin)
        # self.bus.on('enclosure.eyes.timedspin', self.timed_spin)
        # self.bus.on('enclosure.eyes.reset', self.reset)
        # self.bus.on('enclosure.eyes.setpixel', self.set_pixel)
        # self.bus.on('enclosure.eyes.fill', self.fill)

        # self.bus.on('enclosure.mouth.reset', self.reset)
        # self.bus.on('enclosure.mouth.talk', self.talk)
        # self.bus.on('enclosure.mouth.think', self.think)
        # self.bus.on('enclosure.mouth.listen', self.listen)
        # self.bus.on('enclosure.mouth.smile', self.smile)
        # self.bus.on('enclosure.mouth.viseme', self.viseme)
        # self.bus.on('enclosure.mouth.text', self.text)
        # self.bus.on('enclosure.mouth.display', self.display)
        # self.bus.on('enclosure.mouth.display_image', self.display_image)
        # self.bus.on('enclosure.weather.display', self.display_weather)

        # self.bus.on('recognizer_loop:record_begin', self.mouth.listen)
        # self.bus.on('recognizer_loop:record_end', self.mouth.reset)
        # self.bus.on('recognizer_loop:audio_output_start', self.mouth.talk)
        # self.bus.on('recognizer_loop:audio_output_end', self.mouth.reset)
        pass
Ejemplo n.º 27
0
class QueryBackend(object):
    """
        Base class for all query implementations. waits timeout seconds for
        answer, considered answers are generated as follows

            query:
                deep.dream.request
                deep.dream
            possible responses:
                deep.dream.reply,
                deep.dream.result,
                deep.dream.response

            query:
                face.recognition.request
                face.recognition
            possible responses:
                 face.recognition.reply,
                 face.recognition.result,
                 face.recognition.response

    """

    def __init__(self, name=None, emitter=None, timeout=5, logger=None):
        """
           initialize emitter, register events, initialize internal variables

           args:
                name(str): name identifier
                emitter (WebsocketClient): mycroft messagebus websocket
                timeout (int): maximum seconds to wait for reply
                logger (logger) : custom logger if desired
        """
        if name is None:
            self.name = "QueryBackend"
        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.timeout = timeout
        self.query = None
        if logger is None:
            self.logger = LOG
        else:
            self.logger = logger
        self.waiting_messages = []
        self.elapsed_time = 0
        self.config = Configuration.get().get(self.name, {})
        self.result = {}

    def send_request(self, message_type, message_data=None,
                     message_context=None, response_messages=None):
        """
          prepare query for sending, add several possible kinds of
          response message automatically
                "message_type.reply" ,
                "message_type.response",
                "message_type.result"

        args:
                message_type (str): query message type
                message_data (dict): query message data
                message_context (dict): query message context
                response_messages (list) : list of extra messages to end wait
        """
        if response_messages is None:
            response_messages = []
        # generate reply messages
        self.waiting_messages = response_messages
        if ".request" in message_type:
            response = message_type.replace(".request", ".reply")
            if response not in self.waiting_messages:
                self.waiting_messages.append(response)
            response = message_type.replace(".request", ".response")
            if response not in self.waiting_messages:
                self.waiting_messages.append(response)
            response = message_type.replace(".request", ".result")
            if response not in self.waiting_messages:
                self.waiting_messages.append(response)
        else:
            response = message_type + ".reply"
            if response not in self.waiting_messages:
                self.waiting_messages.append(response)
            response = message_type + ".response"
            if response not in self.waiting_messages:
                self.waiting_messages.append(response)
            response = message_type + ".result"
            if response not in self.waiting_messages:
                self.waiting_messages.append(response)

        # update message context
        if message_context is None:
            message_context = {}
        message_context["source"] = self.name
        message_context["waiting_for"] = self.waiting_messages
        start = time.time()
        self.elapsed_time = 0
        result = self._send_internal_request(message_type, message_data,
                                                   message_context)
        self.elapsed_time = time.time() - start
        return result

    def _send_internal_request(self, message_type, message_data,
                               message_context):
        """
        send query to messagebus

             Args:
                message_type (str): query message type
                message_data (dict): query message data
                message_context (dict): query message context
        """
        self.query = BusQuery(self.emitter, message_type, message_data,
                              message_context)
        for message in self.waiting_messages[1:]:
            self.query.add_response_type(message)
        return self.query.send(self.waiting_messages[0], self.timeout)

    def get_result(self, context=False, type=False):
        """
            return last processed response message data


             Args:
                type (bool): return response message type
                context (bool): return response message context

        """
        if self.query is None:
            return None
        if type:
            return self.query.get_response_type()
        if context:
            return self.query.get_response_context()
        return self.query.get_response_data()

    def shutdown(self):
        """ remove all listeners """
        if self.query:
            self.query.shutdown()
Ejemplo n.º 28
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"))
Ejemplo n.º 29
0
class SkillContainer(object):
    def __init__(self, args):
        params = self.__build_params(args)

        if params.config:
            ConfigurationManager.load_local([params.config])

        if exists(params.lib) and isdir(params.lib):
            sys.path.append(params.lib)

        sys.path.append(params.dir)
        self.dir = params.dir

        self.enable_intent = params.enable_intent

        self.__init_client(params)

    @staticmethod
    def __build_params(args):
        parser = argparse.ArgumentParser()
        parser.add_argument("--config", default="./mycroft.conf")
        parser.add_argument("dir", nargs='?', default=dirname(__file__))
        parser.add_argument("--lib", default="./lib")
        parser.add_argument("--host", default=None)
        parser.add_argument("--port", default=None)
        parser.add_argument("--use-ssl", action='store_true', default=False)
        parser.add_argument("--enable-intent",
                            action='store_true',
                            default=False)
        return parser.parse_args(args)

    def __init_client(self, params):
        config = ConfigurationManager.get().get("websocket")

        if not params.host:
            params.host = config.get('host')
        if not params.port:
            params.port = config.get('port')

        self.ws = WebsocketClient(host=params.host,
                                  port=params.port,
                                  ssl=params.use_ssl)

        # Connect configuration manager to message bus to receive updates
        ConfigurationManager.init(self.ws)

    def load_skill(self):
        if self.enable_intent:
            IntentService(self.ws)

        skill_descriptor = create_skill_descriptor(self.dir)
        self.skill = load_skill(skill_descriptor, self.ws, hash(self.dir))

    def run(self):
        try:
            self.ws.on('message', LOG.debug)
            self.ws.on('open', self.load_skill)
            self.ws.on('error', LOG.error)
            self.ws.run_forever()
        except Exception as e:
            LOG.error("Error: {0}".format(e))
            self.stop()

    def stop(self):
        if self.skill:
            self.skill.shutdown()
Ejemplo n.º 30
0
class SkillContainer(object):
    def __init__(self, args):
        params = self.__build_params(args)

        if params.config:
            Configuration.get([params.config])

        if exists(params.lib) and isdir(params.lib):
            sys.path.append(params.lib)

        sys.path.append(params.dir)
        self.dir = params.dir

        self.enable_intent = params.enable_intent

        self.__init_client(params)

    @staticmethod
    def __build_params(args):
        parser = argparse.ArgumentParser()
        parser.add_argument("--config", default="./mycroft.conf")
        parser.add_argument("dir", nargs='?', default=dirname(__file__))
        parser.add_argument("--lib", default="./lib")
        parser.add_argument("--host", default=None)
        parser.add_argument("--port", default=None)
        parser.add_argument("--use-ssl", action='store_true', default=False)
        parser.add_argument("--enable-intent", action='store_true',
                            default=False)
        return parser.parse_args(args)

    def __init_client(self, params):
        config = Configuration.get().get("websocket")

        if not params.host:
            params.host = config.get('host')
        if not params.port:
            params.port = config.get('port')

        self.ws = WebsocketClient(host=params.host,
                                  port=params.port,
                                  ssl=params.use_ssl)

        # Connect configuration manager to message bus to receive updates
        Configuration.init(self.ws)

    def load_skill(self):
        if self.enable_intent:
            IntentService(self.ws)

        skill_descriptor = create_skill_descriptor(self.dir)
        self.skill = load_skill(skill_descriptor, self.ws, hash(self.dir))

    def run(self):
        try:
            self.ws.on('message', LOG.debug)
            self.ws.on('open', self.load_skill)
            self.ws.on('error', LOG.error)
            self.ws.run_forever()
        except Exception as e:
            LOG.error("Error: {0}".format(e))
            self.stop()

    def stop(self):
        if self.skill:
            self.skill.shutdown()
Ejemplo n.º 31
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()
        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}))
Ejemplo n.º 32
0
class WiFi:
    def __init__(self):
        self.iface = pyw.winterfaces()[0]
        self.ap = AccessPoint(self.iface)
        self.server = None
        self.ws = WebsocketClient()
        ConfigurationManager.init(self.ws)
        self.enclosure = EnclosureAPI(self.ws)
        self.init_events()
        self.conn_monitor = None
        self.conn_monitor_stop = threading.Event()

    def init_events(self):
        '''
            Register handlers for various websocket events used
            to communicate with outside systems.
        '''

        # This event is generated by an outside mechanism.  On a
        # Holmes unit this comes from the Enclosure's menu item
        # being selected.
        self.ws.on('mycroft.wifi.start', self.start)

        # These events are generated by Javascript in the captive
        # portal.
        self.ws.on('mycroft.wifi.stop', self.stop)
        self.ws.on('mycroft.wifi.scan', self.scan)
        self.ws.on('mycroft.wifi.connect', self.connect)

    def start(self, event=None):
        '''
           Fire up the MYCROFT access point for the user to connect to
           with a phone or computer.
        '''
        LOG.info("Starting access point...")

        # Fire up our access point
        self.ap.up()
        LOG.info("Done putting ap up...")
        if not self.server:
            LOG.info("Creating web server...")
            self.server = WebServer(self.ap.ip, 80)
            LOG.info("Starting web server...")
            self.server.start()
            LOG.info("Created web server.")

        LOG.info("Access point started!\n%s" % self.ap.__dict__)
        self._start_connection_monitor()

    def _connection_prompt(self, prefix):
        # let the user know to connect to it...
        passwordSpelled = ",  ".join(self.ap.password)
        self._speak_and_show(
            prefix + " Use your mobile device or computer to "
                     "connect to the wifi network "
                     "'MYCROFT';  Then enter the uppercase "
                     "password " + passwordSpelled,
            self.ap.password)

    def _speak_and_show(self, speak, show):
        ''' Communicate with the user throughout the process '''
        self.ws.emit(Message("speak", {'utterance': speak}))
        if show is None:
            return

        # TODO: This sleep should not be necessary, but without it the
        #       text to be displayed by enclosure.mouth_text() gets
        #       wiped out immediately when the utterance above is
        #       begins processing.
        #       Remove the sleep once this behavior is corrected.
        sleep(0.25)
        self.enclosure.mouth_text(show)

    def _start_connection_monitor(self):
        LOG.info("Starting monitor thread...\n")
        if self.conn_monitor is not None:
            LOG.info("Killing old thread...\n")
            self.conn_monitor_stop.set()
            self.conn_monitor_stop.wait()

        self.conn_monitor = threading.Thread(
            target=self._do_connection_monitor,
            args={})
        self.conn_monitor.daemon = True
        self.conn_monitor.start()
        LOG.info("Monitor thread setup complete.\n")

    def _stop_connection_monitor(self):
        ''' Set flag that will let monitoring thread close '''
        self.conn_monitor_stop.set()

    def _do_connection_monitor(self):
        LOG.info("Invoked monitor thread...\n")
        mtimeLast = os.path.getmtime('/var/lib/misc/dnsmasq.leases')
        bHasConnected = False
        cARPFailures = 0
        timeStarted = time.time()
        timeLastAnnounced = 0  # force first announcement to now
        self.conn_monitor_stop.clear()

        while not self.conn_monitor_stop.isSet():
            # do our monitoring...
            mtime = os.path.getmtime('/var/lib/misc/dnsmasq.leases')
            if mtimeLast != mtime:
                # Something changed in the dnsmasq lease file -
                # presumably a (re)new lease
                bHasConnected = True
                cARPFailures = 0
                mtimeLast = mtime
                timeStarted = time.time()  # reset start time after connection
                timeLastAnnounced = time.time() - 45  # announce how to connect

            if time.time() - timeStarted > 60 * 5:
                # After 5 minutes, shut down the access point
                LOG.info("Auto-shutdown of access point after 5 minutes")
                self.stop()
                continue

            if time.time() - timeLastAnnounced >= 45:
                if bHasConnected:
                    self._speak_and_show(
                        "Now you can open your browser and go to start dot "
                        "mycroft dot A I, then follow the instructions given "
                        " there",
                        "start.mycroft.ai")
                else:
                    self._connection_prompt("Allow me to walk you through the "
                                            " wifi setup process; ")
                timeLastAnnounced = time.time()

            if bHasConnected:
                # Flush the ARP entries associated with our access point
                # This will require all network hardware to re-register
                # with the ARP tables if still present.
                if cARPFailures == 0:
                    res = cli_no_output('ip', '-s', '-s', 'neigh', 'flush',
                                        self.ap.subnet + '.0/24')
                    # Give ARP system time to re-register hardware
                    sleep(5)

                # now look at the hardware that has responded, if no entry
                # shows up on our access point after 2*5=10 seconds, the user
                # has disconnected
                if not self._is_ARP_filled():
                    cARPFailures += 1
                    if cARPFailures > 2:
                        self._connection_prompt("Connection lost,")
                        bHasConnected = False
                else:
                    cARPFailures = 0
            sleep(5)  # wait a bit to prevent thread from hogging CPU

        LOG.info("Exiting monitor thread...\n")
        self.conn_monitor_stop.clear()

    def _is_ARP_filled(self):
        res = cli_no_output('/usr/sbin/arp', '-n')
        out = str(res.get("stdout"))
        if out:
            # Parse output, skipping header
            for o in out.split("\n")[1:]:
                if o[0:len(self.ap.subnet)] == self.ap.subnet:
                    if "(incomplete)" in o:
                        # ping the IP to get the ARP table entry reloaded
                        ip_disconnected = o.split(" ")[0]
                        cli_no_output('/bin/ping', '-c', '1', '-W', '3',
                                      ip_disconnected)
                    else:
                        return True  # something on subnet is connected!
        return False

    def scan(self, event=None):
        LOG.info("Scanning wifi connections...")
        networks = {}
        status = self.get_status()

        for cell in Cell.all(self.iface):
            update = True
            ssid = cell.ssid
            quality = self.get_quality(cell.quality)

            # If there are duplicate network IDs (e.g. repeaters) only
            # report the strongest signal
            if networks.__contains__(ssid):
                update = networks.get(ssid).get("quality") < quality
            if update and ssid:
                networks[ssid] = {
                    'quality': quality,
                    'encrypted': cell.encrypted,
                    'connected': self.is_connected(ssid, status)
                }
        self.ws.emit(Message("mycroft.wifi.scanned",
                             {'networks': networks}))
        LOG.info("Wifi connections scanned!\n%s" % networks)

    @staticmethod
    def get_quality(quality):
        values = quality.split("/")
        return float(values[0]) / float(values[1])

    def connect(self, event=None):
        if event and event.data:
            ssid = event.data.get("ssid")
            connected = self.is_connected(ssid)

            if connected:
                LOG.warn("Mycroft is already connected to %s" % ssid)
            else:
                self.disconnect()
                LOG.info("Connecting to: %s" % ssid)
                nid = wpa(self.iface, 'add_network')
                wpa(self.iface, 'set_network', nid, 'ssid', '"' + ssid + '"')

                if event.data.__contains__("pass"):
                    psk = '"' + event.data.get("pass") + '"'
                    wpa(self.iface, 'set_network', nid, 'psk', psk)
                else:
                    wpa(self.iface, 'set_network', nid, 'key_mgmt', 'NONE')

                wpa(self.iface, 'enable', nid)
                connected = self.get_connected(ssid)
                if connected:
                    wpa(self.iface, 'save_config')

            self.ws.emit(Message("mycroft.wifi.connected",
                                 {'connected': connected}))
            LOG.info("Connection status for %s = %s" % (ssid, connected))

            if connected:
                self.ws.emit(Message("speak", {
                    'utterance': "Thank you, I'm now connected to the "
                                 "internet and ready for use"}))
                # TODO: emit something that triggers a pairing check

    def disconnect(self):
        status = self.get_status()
        nid = status.get("id")
        if nid:
            ssid = status.get("ssid")
            wpa(self.iface, 'disable', nid)
            LOG.info("Disconnecting %s id: %s" % (ssid, nid))

    def get_status(self):
        res = cli('wpa_cli', '-i', self.iface, 'status')
        out = str(res.get("stdout"))
        if out:
            return dict(o.split("=") for o in out.split("\n")[:-1])
        return {}

    def get_connected(self, ssid, retry=5):
        connected = self.is_connected(ssid)
        while not connected and retry > 0:
            sleep(2)
            retry -= 1
            connected = self.is_connected(ssid)
        return connected

    def is_connected(self, ssid, status=None):
        status = status or self.get_status()
        state = status.get("wpa_state")
        return status.get("ssid") == ssid and state == "COMPLETED"

    def stop(self, event=None):
        LOG.info("Stopping access point...")
        self._stop_connection_monitor()
        self.ap.down()
        if self.server:
            self.server.server.shutdown()
            self.server.server.server_close()
            self.server.join()
            self.server = None
        LOG.info("Access point stopped!")

    def _do_net_check(self):
        # give system 5 seconds to resolve network or get plugged in
        sleep(5)

        LOG.info("Checking internet connection again")
        if not connected() and self.conn_monitor is None:
            # TODO: Enclosure/localization
            self._speak_and_show(
                "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", None)

    def run(self):
        try:
            # When the system first boots up, check for a valid internet
            # connection.
            LOG.info("Checking internet connection")
            if not connected():
                LOG.info("No connection initially, waiting 20...")
                self.net_check = threading.Thread(
                    target=self._do_net_check,
                    args={})
                self.net_check.daemon = True
                self.net_check.start()
            else:
                LOG.info("Connection found!")

            self.ws.run_forever()
        except Exception as e:
            LOG.error("Error: {0}".format(e))
            self.stop()
Ejemplo n.º 33
0
class WiFi:
    def __init__(self):
        self.iface = pyw.winterfaces()[0]
        self.ap = AccessPoint(self.iface)
        self.server = None
        self.ws = WebsocketClient()
        ConfigurationManager.init(self.ws)
        self.enclosure = EnclosureAPI(self.ws)
        self.init_events()
        self.conn_monitor = None
        self.conn_monitor_stop = threading.Event()

    def init_events(self):
        '''
            Register handlers for various websocket events used
            to communicate with outside systems.
        '''

        # This event is generated by an outside mechanism.  On a
        # Holmes unit this comes from the Enclosure's menu item
        # being selected.
        self.ws.on('mycroft.wifi.start', self.start)

        # These events are generated by Javascript in the captive
        # portal.
        self.ws.on('mycroft.wifi.stop', self.stop)
        self.ws.on('mycroft.wifi.scan', self.scan)
        self.ws.on('mycroft.wifi.connect', self.connect)

    def start(self, event=None):
        '''
           Fire up the MYCROFT access point for the user to connect to
           with a phone or computer.
        '''
        LOG.info("Starting access point...")

        # Fire up our access point
        self.ap.up()
        if not self.server:
            LOG.info("Creating web server...")
            self.server = WebServer(self.ap.ip, 80)
            LOG.info("Starting web server...")
            self.server.start()
            LOG.info("Created web server.")

        LOG.info("Access point started!\n%s" % self.ap.__dict__)
        self._start_connection_monitor()

    def _connection_prompt(self, prefix):
        # let the user know to connect to it...
        passwordSpelled = ",  ".join(self.ap.password)
        self._speak_and_show(
            prefix + " Use your mobile device or computer to "
                     "connect to the wifi network "
                     "'MYCROFT';  Then enter the uppercase "
                     "password " + passwordSpelled,
            self.ap.password)

    def _speak_and_show(self, speak, show):
        ''' Communicate with the user throughout the process '''
        self.ws.emit(Message("speak", {'utterance': speak}))
        if show is None:
            return

        # TODO: This sleep should not be necessary, but without it the
        #       text to be displayed by enclosure.mouth_text() gets
        #       wiped out immediately when the utterance above is
        #       begins processing.
        #       Remove the sleep once this behavior is corrected.
        sleep(0.25)
        self.enclosure.mouth_text(show)

    def _start_connection_monitor(self):
        LOG.info("Starting monitor thread...\n")
        if self.conn_monitor is not None:
            LOG.info("Killing old thread...\n")
            self.conn_monitor_stop.set()
            self.conn_monitor_stop.wait()

        self.conn_monitor = threading.Thread(
            target=self._do_connection_monitor,
            args={})
        self.conn_monitor.daemon = True
        self.conn_monitor.start()
        LOG.info("Monitor thread setup complete.\n")

    def _stop_connection_monitor(self):
        ''' Set flag that will let monitoring thread close '''
        self.conn_monitor_stop.set()

    def _do_connection_monitor(self):
        LOG.info("Invoked monitor thread...\n")
        mtimeLast = os.path.getmtime('/var/lib/misc/dnsmasq.leases')
        bHasConnected = False
        cARPFailures = 0
        timeStarted = time.time()
        timeLastAnnounced = 0  # force first announcement to now
        self.conn_monitor_stop.clear()

        while not self.conn_monitor_stop.isSet():
            # do our monitoring...
            mtime = os.path.getmtime('/var/lib/misc/dnsmasq.leases')
            if mtimeLast != mtime:
                # Something changed in the dnsmasq lease file -
                # presumably a (re)new lease
                bHasConnected = True
                cARPFailures = 0
                mtimeLast = mtime
                timeStarted = time.time()  # reset start time after connection
                timeLastAnnounced = time.time() - 45  # announce how to connect

            if time.time() - timeStarted > 60 * 5:
                # After 5 minutes, shut down the access point
                LOG.info("Auto-shutdown of access point after 5 minutes")
                self.stop()
                continue

            if time.time() - timeLastAnnounced >= 45:
                if bHasConnected:
                    self._speak_and_show(
                        "Now you can open your browser and go to start dot "
                        "mycroft dot A I, then follow the instructions given "
                        " there",
                        "start.mycroft.ai")
                else:
                    self._connection_prompt("Allow me to walk you through the "
                                            " wifi setup process; ")
                timeLastAnnounced = time.time()

            if bHasConnected:
                # Flush the ARP entries associated with our access point
                # This will require all network hardware to re-register
                # with the ARP tables if still present.
                if cARPFailures == 0:
                    res = cli_no_output('ip', '-s', '-s', 'neigh', 'flush',
                                        self.ap.subnet + '.0/24')
                    # Give ARP system time to re-register hardware
                    sleep(5)

                # now look at the hardware that has responded, if no entry
                # shows up on our access point after 2*5=10 seconds, the user
                # has disconnected
                if not self._is_ARP_filled():
                    cARPFailures += 1
                    if cARPFailures > 2:
                        self._connection_prompt("Connection lost,")
                        bHasConnected = False
                else:
                    cARPFailures = 0
            sleep(5)  # wait a bit to prevent thread from hogging CPU

        LOG.info("Exiting monitor thread...\n")
        self.conn_monitor_stop.clear()

    def _is_ARP_filled(self):
        res = cli_no_output('/usr/sbin/arp', '-n')
        out = str(res.get("stdout"))
        if out:
            # Parse output, skipping header
            for o in out.split("\n")[1:]:
                if o[0:len(self.ap.subnet)] == self.ap.subnet:
                    if "(incomplete)" in o:
                        # ping the IP to get the ARP table entry reloaded
                        ip_disconnected = o.split(" ")[0]
                        cli_no_output('/bin/ping', '-c', '1', '-W', '3',
                                      ip_disconnected)
                    else:
                        return True  # something on subnet is connected!
        return False

    def scan(self, event=None):
        LOG.info("Scanning wifi connections...")
        networks = {}
        status = self.get_status()

        for cell in Cell.all(self.iface):
            update = True
            ssid = cell.ssid
            quality = self.get_quality(cell.quality)

            # If there are duplicate network IDs (e.g. repeaters) only
            # report the strongest signal
            if networks.__contains__(ssid):
                update = networks.get(ssid).get("quality") < quality
            if update and ssid:
                networks[ssid] = {
                    'quality': quality,
                    'encrypted': cell.encrypted,
                    'connected': self.is_connected(ssid, status)
                }
        self.ws.emit(Message("mycroft.wifi.scanned",
                             {'networks': networks}))
        LOG.info("Wifi connections scanned!\n%s" % networks)

    @staticmethod
    def get_quality(quality):
        values = quality.split("/")
        return float(values[0]) / float(values[1])

    def connect(self, event=None):
        if event and event.data:
            ssid = event.data.get("ssid")
            connected = self.is_connected(ssid)

            if connected:
                LOG.warn("Mycroft is already connected to %s" % ssid)
            else:
                self.disconnect()
                LOG.info("Connecting to: %s" % ssid)
                nid = wpa(self.iface, 'add_network')
                wpa(self.iface, 'set_network', nid, 'ssid', '"' + ssid + '"')

                if event.data.__contains__("pass"):
                    psk = '"' + event.data.get("pass") + '"'
                    wpa(self.iface, 'set_network', nid, 'psk', psk)
                else:
                    wpa(self.iface, 'set_network', nid, 'key_mgmt', 'NONE')

                wpa(self.iface, 'enable', nid)
                connected = self.get_connected(ssid)
                if connected:
                    wpa(self.iface, 'save_config')

            self.ws.emit(Message("mycroft.wifi.connected",
                                 {'connected': connected}))
            LOG.info("Connection status for %s = %s" % (ssid, connected))

            if connected:
                self.ws.emit(Message("speak", {
                    'utterance': "Thank you, I'm now connected to the "
                                 "internet and ready for use"}))
                # TODO: emit something that triggers a pairing check

    def disconnect(self):
        status = self.get_status()
        nid = status.get("id")
        if nid:
            ssid = status.get("ssid")
            wpa(self.iface, 'disable', nid)
            LOG.info("Disconnecting %s id: %s" % (ssid, nid))

    def get_status(self):
        res = cli('wpa_cli', '-i', self.iface, 'status')
        out = str(res.get("stdout"))
        if out:
            return dict(o.split("=") for o in out.split("\n")[:-1])
        return {}

    def get_connected(self, ssid, retry=5):
        connected = self.is_connected(ssid)
        while not connected and retry > 0:
            sleep(2)
            retry -= 1
            connected = self.is_connected(ssid)
        return connected

    def is_connected(self, ssid, status=None):
        status = status or self.get_status()
        state = status.get("wpa_state")
        return status.get("ssid") == ssid and state == "COMPLETED"

    def stop(self, event=None):
        LOG.info("Stopping access point...")
        self._stop_connection_monitor()
        self.ap.down()
        if self.server:
            self.server.server.shutdown()
            self.server.server.server_close()
            self.server.join()
            self.server = None
        LOG.info("Access point stopped!")

    def _do_net_check(self):
        # give system 5 seconds to resolve network or get plugged in
        sleep(5)

        LOG.info("Checking internet connection again")
        if not connected() and self.conn_monitor is None:
            # TODO: Enclosure/localization
            self._speak_and_show(
                "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", None)

    def run(self):
        try:
            # When the system first boots up, check for a valid internet
            # connection.
            LOG.info("Checking internet connection")
            if not connected():
                LOG.info("No connection initially, waiting 20...")
                self.net_check = threading.Thread(
                    target=self._do_net_check,
                    args={})
                self.net_check.daemon = True
                self.net_check.start()
            else:
                LOG.info("Connection found!")

            self.ws.run_forever()
        except Exception as e:
            LOG.error("Error: {0}".format(e))
            self.stop()
Ejemplo n.º 34
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"))
Ejemplo n.º 35
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 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()
Ejemplo n.º 36
0
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
Ejemplo n.º 37
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()
        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()
Ejemplo n.º 38
0
class LocalListener(object):
    def __init__(self,
                 hmm=None,
                 lm=None,
                 le_dict=None,
                 lang="en-us",
                 emitter=None,
                 debug=False):
        self.lang = lang
        self.decoder = None
        self.listening = False
        self.emitter = emitter
        self.event_thread = None
        self.async_thread = None
        # get hmm, already in mycroft
        self.hmm = MYCROFT_ROOT_PATH + '/mycroft/client/speech/recognizer/model/en-us/hmm/'
        # load wav config
        self.audioconfig = ConfigurationManager.get()
        self.reset_decoder()

        if not debug:
            ERROR_HANDLER_FUNC = CFUNCTYPE(None, c_char_p, c_int, c_char_p,
                                           c_int, c_char_p)

            def py_error_handler(filename, line, function, err, fmt):
                ignores = [0, 2, 16, 77]
                if err not in ignores:
                    print err, fmt

            c_error_handler = ERROR_HANDLER_FUNC(py_error_handler)

            @contextmanager
            def noalsaerr():
                asound = cdll.LoadLibrary('libasound.so')
                asound.snd_lib_error_set_handler(c_error_handler)
                yield
                asound.snd_lib_error_set_handler(None)

            with noalsaerr():
                self.p = pyaudio.PyAudio()
        else:
            self.p = pyaudio.PyAudio()
        self.stream = self.p.open(format=pyaudio.paInt16,
                                  channels=1,
                                  rate=16000,
                                  input=True,
                                  frames_per_buffer=1024)

        if self.emitter is None:
            self.emitter = WebsocketClient()

            def connect():
                # Once the websocket has connected, just watch it for events
                self.emitter.run_forever()

            self.event_thread = Thread(target=connect)
            self.event_thread.setDaemon(True)
            self.event_thread.start()
            sleep(2)

    def emit(self, message, data=None, context=None):
        if self.emitter is not None:
            data = data or {}
            context = context or {"source": "LocalListener"}
            self.emitter.emit(Message(message, data, context))

    def handle_record_begin(self):
        # If enabled, play a wave file with a short sound to audibly
        # indicate recording has begun.
        if self.audioconfig.get('confirm_listening'):
            file = resolve_resource_file(
                self.audioconfig.get('sounds').get('start_listening'))
            if file:
                play_wav(file)
        LOG.info("deactivating speech recognition")
        self.emit("recognizer_loop:sleep")
        self.emit("recognizer_loop:local_listener.start")
        self.emit('recognizer_loop:record_begin')

    def handle_record_end(self):
        print("End Recording...")
        self.emit('recognizer_loop:record_end')
        LOG.info("reactivating speech recognition")
        self.emit("recognizer_loop:local_listener.end")
        self.emit("recognizer_loop:wake_up")

    def reset_decoder(self, hmm=None, lm=None, le_dict=None):
        self.lastutt = None
        LOG.info("resetting decoder")
        lang = self.lang
        le_dict = le_dict or join(dirname(__file__), lang, '9794.dic')
        if self.lang == 'en-us':
            hmm = self.hmm
        else:
            hmm = hmm or join(dirname(__file__), lang, 'hmm')
        lm = lm or join(dirname(__file__), lang, '9794.lm')
        self.config = Decoder.default_config()
        self.config.set_string('-hmm', hmm)
        self.config.set_string('-lm', lm)
        self.config.set_string('-dict', le_dict)
        self.config.set_string('-logfn', '/dev/null')
        self.decoder = Decoder(self.config)

    def listen_once_async(self):
        LOG.info("starting async local listening")
        self.async_thread_ = Thread(target=self._async_listen_once)
        self.async_thread_.setDaemon(True)
        self.async_thread_.start()

    def listen_async(self):
        LOG.info("starting async local listening")
        self.async_thread = Thread(target=self._async_listen)
        self.async_thread.setDaemon(True)
        self.async_thread.start()

    def _async_listen(self):
        for ut in self.listen():
            if ut is not None:
                print "emitting to bus:", ut
                self.emit("recognizer_loop:utterance", {
                    "utterances": [ut.lower()],
                    "lang": self.lang
                })

    def _async_listen_once(self):
        ut = self.listen_once()
        if ut is not None:
            print "emitting to bus:", ut
            self.emit("recognizer_loop:utterance", {
                "utterances": [ut.lower()],
                "lang": self.lang
            })

    def listen(self):
        self.reset_decoder()
        self.handle_record_begin()
        self.stream.start_stream()
        self.listening = True
        in_speech_bf = False
        self.decoder.start_utt()
        LOG.info("continuous listening")
        while self.listening:
            buf = self.stream.read(1024)
            if buf:
                self.decoder.process_raw(buf, False, False)
                if self.decoder.get_in_speech() != in_speech_bf:
                    in_speech_bf = self.decoder.get_in_speech()
                    if not in_speech_bf:
                        self.decoder.end_utt()
                        utt = self.decoder.hyp().hypstr
                        self.decoder.start_utt()
                        if utt.strip() != '':
                            reply = utt.strip()
                            self.lastutt = reply
                            yield reply
            else:
                break
        self.shutdown()

    def listen_once(self):
        ut = self._listen_once()
        return ''.join(ut)

    def _listen_once(self):
        self.reset_decoder()
        self.handle_record_begin()
        self.stream.start_stream()
        self.listening = True
        in_speech_bf = False
        self.decoder.start_utt()
        LOG.info("listening once")
        while self.listening:
            buf = self.stream.read(1024)
            if buf:
                self.decoder.process_raw(buf, False, False)
                if self.decoder.get_in_speech() != in_speech_bf:
                    in_speech_bf = self.decoder.get_in_speech()
                    if not in_speech_bf:
                        self.decoder.end_utt()
                        utt = self.decoder.hyp().hypstr
                        self.decoder.start_utt()
                        if utt.strip() != '':
                            reply = utt.strip()
                            yield reply
                            self.lastutt = reply
                            self.listening = False
            else:
                break
        self.shutdown()

    def listen_numbers(self, configpath=None):
        LOG.info("listening for numbers")
        for number in self.listen_specialized(
                config=self.numbers_config(configpath)):
            yield number

    def listen_numbers_once(self, configpath=None):
        LOG.info("listening for numbers once")
        return self.listen_once_specialized(
            config=self.numbers_config(configpath))

    def listen_specialized(self, dictionary=None, config=None):
        self.reset_decoder()
        if config is None:
            config = self.config
        else:
            LOG.info("loading custom decoder config")
        if dictionary is not None:
            LOG.info("loading custom dictionary")
            config.set_string('-dict', self.create_dict(dictionary))
            print dictionary.keys()
        self.decoder = Decoder(config)
        self.handle_record_begin()
        self.stream.start_stream()
        self.listening = True
        in_speech_bf = False
        self.decoder.start_utt()
        LOG.info("continuous listening")
        while self.listening:
            buf = self.stream.read(1024)
            if buf:
                self.decoder.process_raw(buf, False, False)
                if self.decoder.get_in_speech() != in_speech_bf:
                    in_speech_bf = self.decoder.get_in_speech()
                    if not in_speech_bf:
                        self.decoder.end_utt()
                        utt = self.decoder.hyp().hypstr
                        self.decoder.start_utt()
                        if utt.strip() != '':
                            reply = utt.strip()
                            self.lastutt = reply
                            yield reply
            else:
                break
        self.shutdown()

    def listen_once_specialized(self, dictionary=None, config=None):
        ut = self._listen_once_specialized(dictionary, config)
        return ''.join(ut)

    def _listen_once_specialized(self, dictionary=None, config=None):
        self.reset_decoder()
        if config is None:
            config = self.config
        else:
            LOG.info("loading custom decoder config")
        if dictionary is not None:
            LOG.info("loading custom dictionary")
            config.set_string('-dict', self.create_dict(dictionary))
            print dictionary.keys()
        self.decoder = Decoder(config)
        self.handle_record_begin()
        self.stream.start_stream()
        self.listening = True
        in_speech_bf = False
        self.decoder.start_utt()
        LOG.info("listening once")
        while self.listening:
            buf = self.stream.read(1024)
            if buf:
                self.decoder.process_raw(buf, False, False)
                if self.decoder.get_in_speech() != in_speech_bf:
                    in_speech_bf = self.decoder.get_in_speech()
                    if not in_speech_bf:
                        self.decoder.end_utt()
                        utt = self.decoder.hyp().hypstr
                        self.decoder.start_utt()
                        if utt.strip() != '':
                            reply = utt.strip()
                            yield reply
                            self.lastutt = reply
                            self.listening = False

            else:
                break
        self.shutdown()

    def stop_listening(self):
        if self.async_thread:
            LOG.info('stopping async thread')
            self.async_thread.join(timeout=1)
            self.listening = False
            self.async_thread = None
            return True
        return False

    def numbers_config(self, numbers):
        print('running number config')
        numbers = numbers or join(dirname(__file__), self.lang, 'numbers.dic')

        if not exists(numbers):
            if self.lang.startswith("en"):
                numbers = self.create_dict({
                    "ONE": "W AH N",
                    "TWO": "T UW",
                    "THREE": "TH R IY",
                    "FOUR": "F AO R",
                    "FIVE": "F AY V",
                    "SIX": "S IH K S",
                    "SEVEN": "S EH V AH N",
                    "EIGHT": "EY T",
                    "NINE": "N AY N",
                    "TEN": "T EH N"
                })
            else:
                raise NotImplementedError
        config = self.config
        config.set_string('-dict', numbers)
        return config

    def create_dict(self, phonemes_dict):
        (fd, file_name) = tempfile.mkstemp()
        with fdopen(fd, 'w') as f:
            for key_phrase in phonemes_dict:
                phonemes = phonemes_dict[key_phrase]
                words = key_phrase.split()
                phoneme_groups = phonemes.split('.')
                for word, phoneme in zip(words, phoneme_groups):
                    f.write(word + ' ' + phoneme + '\n')
        return file_name

    def shutdown(self):
        self.decoder.end_utt()
        self.handle_record_end()
        self.stream.stop_stream()
        self.stream.close()
        if self.event_thread:
            LOG.info('disconnecting from bus')
            self.event_thread.join(timeout=1)
            self.event_thread = None
        if self.async_thread:
            LOG.info('stopping async thread')
            self.async_thread.join(timeout=1)
            self.listening = False
            self.async_thread = None
        LOG.info('stopping local recognition')
        self.decoder = None
        self.stream = None
        if self.p:
            self.p.terminate()
Ejemplo n.º 39
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()
Ejemplo n.º 40
0
class WiFi:
    def __init__(self):
        self.iface = pyw.winterfaces()[0]
        self.ap = AccessPoint(self.iface)
        self.server = None
        self.ws = WebsocketClient()
        self.enclosure = EnclosureAPI(self.ws)
        self.init_events()
        self.conn_monitor = None
        self.conn_monitor_stop = threading.Event()
        self.starting = False

    def init_events(self):
        '''
            Register handlers for various websocket events used
            to communicate with outside systems.
        '''

        # This event is generated by an outside mechanism.  On a
        # Mark 1 unit this comes from the Enclosure's WIFI menu
        # item being selected.
        self.ws.on('mycroft.wifi.start', self.start)

        # Similar to the above.  Resets to factory defaults
        self.ws.on('mycroft.wifi.reset', self.reset)

        # Similar to the above.  Enable/disable SSH
        self.ws.on('mycroft.enable.ssh', self.ssh_enable)
        self.ws.on('mycroft.disable.ssh', self.ssh_disable)

        # These events are generated by Javascript in the captive
        # portal.
        self.ws.on('mycroft.wifi.stop', self.stop)
        self.ws.on('mycroft.wifi.scan', self.scan)
        self.ws.on('mycroft.wifi.connect', self.connect)

    def start(self, event=None):
        '''
           Fire up the MYCROFT access point for the user to connect to
           with a phone or computer.
        '''
        if self.starting:
            return

        self.starting = True
        LOG.info("Starting access point...")

        self.intro_msg = ""
        if event and event.data.get("msg"):
            self.intro_msg = event.data.get("msg")
        self.allow_timeout = True
        if event and event.data.get("allow_timeout"):
            self.allow_timeout = event.data.get("allow_timeout")

        # Fire up our access point
        self.ap.up()
        if not self.server:
            LOG.info("Creating web server...")
            self.server = WebServer(self.ap.ip, 80)
            LOG.info("Starting web server...")
            self.server.start()
            LOG.info("Created web server.")

        LOG.info("Access point started!\n%s" % self.ap.__dict__)
        self._start_connection_monitor()

    def _connection_prompt(self, intro):
        while self.ap.password is None or self.ap.password == "":
            sleep(1)  # give it time to load

        # Speak the connection instructions and show the password on screen
        passwordSpelled = ",  ".join(self.ap.password)
        self._speak_and_show(intro +
                             " Use your mobile device or computer to connect "
                             "to the wifi network 'MYCROFT'.  Then enter the "
                             "password " + passwordSpelled,
                             self.ap.password)

    def _speak_and_show(self, speak, show):
        ''' Communicate with the user throughout the process '''
        self.ws.emit(Message("speak", {'utterance': speak}))
        if show is None:
            return

        wait_while_speaking()
        self.enclosure.mouth_text(show)

    def _start_connection_monitor(self):
        LOG.info("Starting monitor thread...\n")
        if self.conn_monitor is not None:
            LOG.info("Killing old thread...\n")
            self.conn_monitor_stop.set()
            self.conn_monitor_stop.wait()

        self.conn_monitor = threading.Thread(
            target=self._do_connection_monitor,
            args={})
        self.conn_monitor.daemon = True
        self.conn_monitor.start()
        LOG.info("Monitor thread setup complete.\n")

    def _stop_connection_monitor(self):
        ''' Set flag that will let monitoring thread close '''
        self.conn_monitor_stop.set()

    def _do_connection_monitor(self):
        LOG.info("Invoked monitor thread...\n")
        mtimeLast = os.path.getmtime('/var/lib/misc/dnsmasq.leases')
        bHasConnected = False
        cARPFailures = 0
        timeStarted = time.time()
        timeLastAnnounced = timeStarted - 45  # first reminder in 90 secs
        self.conn_monitor_stop.clear()

        while not self.conn_monitor_stop.isSet():
            # do our monitoring...
            mtime = os.path.getmtime('/var/lib/misc/dnsmasq.leases')
            if mtimeLast != mtime:
                # Something changed in the dnsmasq lease file -
                # presumably a (re)new lease
                bHasConnected = True
                cARPFailures = 0
                mtimeLast = mtime
                timeStarted = time.time()  # reset start time after connection
                timeLastAnnounced = time.time() - 45  # announce how to connect

            if time.time() - timeStarted > 60 * 5 and self.allow_timeout:
                # After 5 minutes, shut down the access point (unless the
                # system has never been setup, in which case we stay up
                # indefinitely)
                LOG.info("Auto-shutdown of access point after 5 minutes")
                self.stop()
                continue

            if time.time() - timeLastAnnounced >= 45:
                if bHasConnected:
                    self._speak_and_show(
                        "Follow the prompt on your mobile device or computer "
                        "and choose a wifi network.  If you don't get a "
                        "prompt, open your browser and go to start dot "
                        "mycroft dot A I.", "start.mycroft.ai")
                else:
                    if self.intro_msg:
                        self._connection_prompt(self.intro_msg)
                        self.intro_msg = None  # only speak the intro once
                    else:
                        self._connection_prompt("Allow me to walk you through "
                                                "the wifi setup process.")

                timeLastAnnounced = time.time()

            if bHasConnected:
                # Flush the ARP entries associated with our access point
                # This will require all network hardware to re-register
                # with the ARP tables if still present.
                if cARPFailures == 0:
                    res = cli_no_output('ip', '-s', '-s', 'neigh', 'flush',
                                        self.ap.subnet + '.0/24')
                    # Give ARP system time to re-register hardware
                    sleep(5)

                # now look at the hardware that has responded, if no entry
                # shows up on our access point after 2*5=10 seconds, the user
                # has disconnected
                if not self._is_ARP_filled():
                    cARPFailures += 1
                    if cARPFailures > 2:
                        self._connection_prompt("Connection lost.")
                        bHasConnected = False
                else:
                    cARPFailures = 0
            sleep(5)  # wait a bit to prevent thread from hogging CPU

        LOG.info("Exiting monitor thread...\n")
        self.conn_monitor_stop.clear()

    def _is_ARP_filled(self):
        res = cli_no_output('/usr/sbin/arp', '-n')
        out = str(res.get("stdout"))
        if out:
            # Parse output, skipping header
            for o in out.split("\n")[1:]:
                if o[0:len(self.ap.subnet)] == self.ap.subnet:
                    if "(incomplete)" in o:
                        # ping the IP to get the ARP table entry reloaded
                        ip_disconnected = o.split(" ")[0]
                        cli_no_output('/bin/ping', '-c', '1', '-W', '3',
                                      ip_disconnected)
                    else:
                        return True  # something on subnet is connected!
        return False

    def scan(self, event=None):
        LOG.info("Scanning wifi connections...")
        networks = {}
        status = self.get_status()

        for cell in Cell.all(self.iface):
            if "x00" in cell.ssid:
                continue  # ignore hidden networks
            update = True
            ssid = cell.ssid
            quality = self.get_quality(cell.quality)

            # If there are duplicate network IDs (e.g. repeaters) only
            # report the strongest signal
            if networks.__contains__(ssid):
                update = networks.get(ssid).get("quality") < quality
            if update and ssid:
                networks[ssid] = {
                    'quality': quality,
                    'encrypted': cell.encrypted,
                    'connected': self.is_connected(ssid, status)
                }
        self.ws.emit(Message("mycroft.wifi.scanned",
                             {'networks': networks}))
        LOG.info("Wifi connections scanned!\n%s" % networks)

    @staticmethod
    def get_quality(quality):
        values = quality.split("/")
        return float(values[0]) / float(values[1])

    def connect(self, event=None):
        if event and event.data:
            ssid = event.data.get("ssid")
            connected = self.is_connected(ssid)

            if connected:
                LOG.warn("Mycroft is already connected to %s" % ssid)
            else:
                self.disconnect()
                LOG.info("Connecting to: %s" % ssid)
                nid = wpa(self.iface, 'add_network')
                wpa(self.iface, 'set_network', nid, 'ssid', '"' + ssid + '"')

                if event.data.__contains__("pass"):
                    psk = '"' + event.data.get("pass") + '"'
                    wpa(self.iface, 'set_network', nid, 'psk', psk)
                else:
                    wpa(self.iface, 'set_network', nid, 'key_mgmt', 'NONE')

                wpa(self.iface, 'enable', nid)
                connected = self.get_connected(ssid)
                if connected:
                    wpa(self.iface, 'save_config')

            self.ws.emit(Message("mycroft.wifi.connected",
                                 {'connected': connected}))
            LOG.info("Connection status for %s = %s" % (ssid, connected))

    def disconnect(self):
        status = self.get_status()
        nid = status.get("id")
        if nid:
            ssid = status.get("ssid")
            wpa(self.iface, 'disable', nid)
            LOG.info("Disconnecting %s id: %s" % (ssid, nid))

    def get_status(self):
        res = cli('wpa_cli', '-i', self.iface, 'status')
        out = str(res.get("stdout"))
        if out:
            return dict(o.split("=") for o in out.split("\n")[:-1])
        return {}

    def get_connected(self, ssid, retry=5):
        connected = self.is_connected(ssid)
        while not connected and retry > 0:
            sleep(2)
            retry -= 1
            connected = self.is_connected(ssid)
        return connected

    def is_connected(self, ssid, status=None):
        status = status or self.get_status()
        state = status.get("wpa_state")
        return status.get("ssid") == ssid and state == "COMPLETED"

    def stop(self, event=None):
        LOG.info("Stopping access point...")
        if is_speaking():
            stop_speaking()  # stop any assistance being spoken
        self._stop_connection_monitor()
        self.ap.down()
        self.enclosure.mouth_reset()  # remove "start.mycroft.ai"
        self.starting = False
        if self.server:
            self.server.server.shutdown()
            self.server.server.server_close()
            self.server.join()
            self.server = None
        LOG.info("Access point stopped!")

    def run(self):
        try:
            self.ws.run_forever()
        except Exception as e:
            LOG.error("Error: {0}".format(e))
            self.stop()

    def reset(self, event=None):
        """Reset the unit to the factory defaults """
        LOG.info("Resetting the WPA_SUPPLICANT File")
        try:
            call(
                "echo '" + WPA_SUPPLICANT +
                "'> /etc/wpa_supplicant/wpa_supplicant.conf",
                shell=True)
            # UGLY BUT WORKS
            call(RM_SKILLS)
        except Exception as e:
            LOG.error("Error: {0}".format(e))

    def ssh_enable(self, event=None):
        LOG.info("Enabling SSH")
        try:
            call('systemctl enable ssh.service', shell=True)
            call('systemctl start ssh.service', shell=True)
        except Exception as e:
            LOG.error("Error: {0}".format(e))

    def ssh_disable(self, event=None):
        LOG.info("Disabling SSH")
        try:
            call('systemctl stop ssh.service', shell=True)
            call('systemctl disable ssh.service', shell=True)
        except Exception as e:
            LOG.error("Error: {0}".format(e))
Ejemplo n.º 41
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 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 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