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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
class WebSocketHandler(tornado.websocket.WebSocketHandler): peer = "unknown" def create_internal_emitter(self): # connect to mycroft internal websocket self.emitter = WebsocketClient() self.register_internal_messages() self.emitter_thread = Thread(target=self.connect_to_internal_emitter) self.emitter_thread.setDaemon(True) self.emitter_thread.start() def register_internal_messages(self): # catch all messages self.emitter.on('speak', self.handle_speak) def connect_to_internal_emitter(self): self.emitter.run_forever() def open(self): LOG.info('Client IP: ' + self.request.remote_ip) self.peer = self.request.remote_ip clients[self.peer] = self self.create_internal_emitter() self.write_message("Welcome to Jarbas Web Client") def on_message(self, message): utterance = message.strip() LOG.info("Utterance : " + utterance) if utterance: if utterance == '"mic_on"': create_signal('startListening') else: data = {"utterances": [utterance], "lang": lang} context = { "source": self.peer, "destinatary": "skills", "client_name": platform, "peer": self.peer } self.emitter.emit( Message("recognizer_loop:utterance", data, context)) def handle_speak(self, event): if event.context.get("client_name", platform) == platform: peer = event.context.get("peer", "") if peer == self.peer: self.write_message(event.data['utterance']) def on_close(self): global clients self.emitter.remove("speak", self.handle_speak) clients.pop(self.peer)
class 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()
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()
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()
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()
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()
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()
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): self.ws = WebsocketClient() ConfigurationManager.init(self.ws) self.config = ConfigurationManager.get().get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws) self.writer = EnclosureWriter(self.serial, self.ws) self.writer.write("system.version") self.ws.on("enclosure.start", self.start) self.started = False Timer(5, self.stop).start() # WHY? This at least needs an explaination, this is non-obvious behavior def start(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.started = True def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + self.port) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def stop(self): if not self.started: self.writer.stop() self.reader.stop() self.serial.close() self.ws.close()
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): self.ws = WebsocketClient() ConfigurationManager.init(self.ws) self.config = ConfigurationManager.get().get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws) self.writer = EnclosureWriter(self.serial, self.ws) self.writer.write("system.version") self.ws.on("enclosure.start", self.start) self.started = False Timer(5, self.stop).start() def start(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() #self.update() self.test() self.started = True def update(self): if self.config.get("update"): try: self.speak("Upgrading enclosure version") subprocess.check_call("/opt/enclosure/upload.sh") self.speak("Enclosure update completed") ConfigurationManager.save({"enclosure": {"update": False}}) except: self.speak("I cannot upgrade right now, I'll try later") def test(self): if self.config.get("test"): self.speak("Beginning hardware test") self.writer.write("test.begin") ConfigurationManager.save({"enclosure": {"test": False}}) def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url(url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + self.port) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def stop(self): if not self.started: self.writer.stop() self.reader.stop() self.serial.close() self.ws.close()
class 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()
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
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
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()
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
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()
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ _last_internet_notification = 0 def __init__(self): self.ws = WebsocketClient() self.ws.on("open", self.on_ws_open) Configuration.init(self.ws) global_config = Configuration.get() self.lang = global_config['lang'] self.config = global_config.get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws, self.lang) self.writer = EnclosureWriter(self.serial, self.ws) # initiates the web sockets on display manager # NOTE: this is a temporary place to initiate display manager sockets initiate_display_manager_ws() def on_ws_open(self, event=None): # Mark 1 auto-detection: # # Prepare to receive message when the Arduino responds to the # following "system.version" self.ws.on("enclosure.started", self.on_arduino_responded) self.arduino_responded = False # Send a message to the Arduino across the serial line asking # for a reply with version info. self.writer.write("system.version") # Start a 5 second timer. If the serial port hasn't received # any acknowledgement of the "system.version" within those # 5 seconds, assume there is nothing on the other end (e.g. # we aren't running a Mark 1 with an Arduino) Timer(5, self.check_for_response).start() # Notifications from mycroft-core self.ws.on("enclosure.notify.no_internet", self.on_no_internet) def on_arduino_responded(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.arduino_responded = True # verify internet connection and prompt user on bootup if needed if not connected(): # We delay this for several seconds to ensure that the other # clients are up and connected to the messagebus in order to # receive the "speak". This was sometimes happening too # quickly and the user wasn't notified what to do. Timer(5, self._do_net_check).start() Timer(60, self._hack_check_for_duplicates).start() def on_no_internet(self, event=None): if connected(): # One last check to see if connection was established return if time.time() - Enclosure._last_internet_notification < 30: # don't bother the user with multiple notifications with 30 secs return Enclosure._last_internet_notification = time.time() # TODO: This should go into EnclosureMark1 subclass of Enclosure. if has_been_paired(): # Handle the translation within that code. self.ws.emit(Message("speak", { 'utterance': "This device is not connected to the Internet. " "Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu"})) else: # enter wifi-setup mode automatically self.ws.emit(Message('system.wifi.setup', {'lang': self.lang})) def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: "+str(self.port)) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def check_for_response(self): if not self.arduino_responded: # There is nothing on the other end of the serial port # close these serial-port readers and this process self.writer.stop() self.reader.stop() self.serial.close() self.ws.close() def _handle_pairing_complete(self, Message): """ Handler for 'mycroft.paired', unmutes the mic after the pairing is complete. """ self.ws.emit(Message("mycroft.mic.unmute")) def _do_net_check(self): # TODO: This should live in the derived Enclosure, e.g. Enclosure_Mark1 LOG.info("Checking internet connection") if not connected(): # and self.conn_monitor is None: if has_been_paired(): # TODO: Enclosure/localization self.speak("This unit is not connected to the Internet. " "Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu") else: # Begin the unit startup process, this is the first time it # is being run with factory defaults. # TODO: This logic should be in Enclosure_Mark1 # TODO: Enclosure/localization # Don't listen to mic during this out-of-box experience self.ws.emit(Message("mycroft.mic.mute")) # Setup handler to unmute mic at the end of on boarding # i.e. after pairing is complete self.ws.once('mycroft.paired', self._handle_pairing_complete) self.speak(mycroft.dialog.get('mycroft.intro')) wait_while_speaking() time.sleep(2) # a pause sounds better than just jumping in # Kick off wifi-setup automatically data = {'allow_timeout': False, 'lang': self.lang} self.ws.emit(Message('system.wifi.setup', data)) def _hack_check_for_duplicates(self): # TEMPORARY HACK: Look for multiple instance of the # mycroft-speech-client and/or mycroft-skills services, which could # happen when upgrading a shipping Mark 1 from release 0.8.17 or # before. When found, force the unit to reboot. import psutil LOG.info("Hack to check for duplicate service instances") count_instances = 0 needs_reboot = False for process in psutil.process_iter(): if process.cmdline() == ['python2.7', '/usr/local/bin/mycroft-speech-client']: count_instances += 1 if (count_instances > 1): LOG.info("Duplicate mycroft-speech-client found") needs_reboot = True count_instances = 0 for process in psutil.process_iter(): if process.cmdline() == ['python2.7', '/usr/local/bin/mycroft-skills']: count_instances += 1 if (count_instances > 1): LOG.info("Duplicate mycroft-skills found") needs_reboot = True if needs_reboot: LOG.info("Hack reboot...") self.reader.process("unit.reboot") self.ws.emit(Message("enclosure.eyes.spin")) self.ws.emit(Message("enclosure.mouth.reset"))
class 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()
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()
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ _last_internet_notification = 0 def __init__(self): self.ws = WebsocketClient() ConfigurationManager.init(self.ws) self.config = ConfigurationManager.instance().get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws) self.writer = EnclosureWriter(self.serial, self.ws) # Send a message to the Arduino across the serial line asking # for a reply with version info. self.writer.write("system.version") # When the Arduino responds, it will generate this message self.ws.on("enclosure.started", self.on_arduino_responded) self.arduino_responded = False # Start a 5 second timer. If the serial port hasn't received # any acknowledgement of the "system.version" within those # 5 seconds, assume there is nothing on the other end (e.g. # we aren't running a Mark 1 with an Arduino) Timer(5, self.check_for_response).start() # Notifications from mycroft-core self.ws.on("enclosure.notify.no_internet", self.on_no_internet) def on_arduino_responded(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.arduino_responded = True # verify internet connection and prompt user on bootup if needed if not connected(): # We delay this for several seconds to ensure that the other # clients are up and connected to the messagebus in order to # receive the "speak". This was sometimes happening too # quickly and the user wasn't notified what to do. Timer(5, self._do_net_check).start() def on_no_internet(self, event=None): if connected(): # One last check to see if connection was established return if time.time()-Enclosure._last_internet_notification < 30: # don't bother the user with multiple notifications with 30 secs return Enclosure._last_internet_notification = time.time() # TODO: This should go into EnclosureMark1 subclass of Enclosure. if has_been_paired(): # Handle the translation within that code. self.ws.emit(Message("speak", { 'utterance': "This device is not connected to the Internet. " "Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu"})) else: # enter wifi-setup mode automatically self.ws.emit(Message("mycroft.wifi.start")) def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + self.port) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def check_for_response(self): if not self.arduino_responded: # There is nothing on the other end of the serial port # close these serial-port readers and this process self.writer.stop() self.reader.stop() self.serial.close() self.ws.close() def _do_net_check(self): # TODO: This should live in the derived Enclosure, e.g. Enclosure_Mark1 LOG.info("Checking internet connection") if not connected(): # and self.conn_monitor is None: if has_been_paired(): # TODO: Enclosure/localization self.ws.emit(Message("speak", { 'utterance': "This unit is not connected to the Internet." " Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu" })) else: # Begin the unit startup process, this is the first time it # is being run with factory defaults. # TODO: This logic should be in Enclosure_Mark1 # TODO: Enclosure/localization # Don't listen to mic during this out-of-box experience self.ws.emit(Message("mycroft.mic.mute", None)) # Kick off wifi-setup automatically self.ws.emit(Message("mycroft.wifi.start", {'msg': "Hello I am Mycroft, your new " "assistant. To assist you I need to be " "connected to the internet. You can " "either plug me in with a network cable," " or use wifi. To setup wifi ", 'allow_timeout': False}))
class 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()
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()
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ _last_internet_notification = 0 def __init__(self): self.ws = WebsocketClient() self.ws.on("open", self.on_ws_open) Configuration.init(self.ws) global_config = Configuration.get() self.lang = global_config['lang'] self.config = global_config.get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws, self.lang) self.writer = EnclosureWriter(self.serial, self.ws) # initiates the web sockets on display manager # NOTE: this is a temporary place to initiate display manager sockets initiate_display_manager_ws() def on_ws_open(self, event=None): # Mark 1 auto-detection: # # Prepare to receive message when the Arduino responds to the # following "system.version" self.ws.on("enclosure.started", self.on_arduino_responded) self.arduino_responded = False # Send a message to the Arduino across the serial line asking # for a reply with version info. self.writer.write("system.version") # Start a 5 second timer. If the serial port hasn't received # any acknowledgement of the "system.version" within those # 5 seconds, assume there is nothing on the other end (e.g. # we aren't running a Mark 1 with an Arduino) Timer(5, self.check_for_response).start() # Notifications from mycroft-core self.ws.on("enclosure.notify.no_internet", self.on_no_internet) def on_arduino_responded(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.arduino_responded = True # verify internet connection and prompt user on bootup if needed if not connected(): # We delay this for several seconds to ensure that the other # clients are up and connected to the messagebus in order to # receive the "speak". This was sometimes happening too # quickly and the user wasn't notified what to do. Timer(5, self._do_net_check).start() Timer(60, self._hack_check_for_duplicates).start() def on_no_internet(self, event=None): if connected(): # One last check to see if connection was established return if time.time() - Enclosure._last_internet_notification < 30: # don't bother the user with multiple notifications with 30 secs return Enclosure._last_internet_notification = time.time() # TODO: This should go into EnclosureMark1 subclass of Enclosure. if has_been_paired(): # Handle the translation within that code. self.ws.emit( Message( "speak", { 'utterance': "This device is not connected to the Internet. " "Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu" })) else: # enter wifi-setup mode automatically self.ws.emit(Message('system.wifi.setup', {'lang': self.lang})) def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url(url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + str(self.port)) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def check_for_response(self): if not self.arduino_responded: # There is nothing on the other end of the serial port # close these serial-port readers and this process self.writer.stop() self.reader.stop() self.serial.close() self.ws.close() def _handle_pairing_complete(self, Message): """ Handler for 'mycroft.paired', unmutes the mic after the pairing is complete. """ self.ws.emit(Message("mycroft.mic.unmute")) def _do_net_check(self): # TODO: This should live in the derived Enclosure, e.g. Enclosure_Mark1 LOG.info("Checking internet connection") if not connected(): # and self.conn_monitor is None: if has_been_paired(): # TODO: Enclosure/localization self.speak("This unit is not connected to the Internet. " "Either plug in a network cable or hold the " "button on top for two seconds, then select " "wifi from the menu") else: # Begin the unit startup process, this is the first time it # is being run with factory defaults. # TODO: This logic should be in Enclosure_Mark1 # TODO: Enclosure/localization # Don't listen to mic during this out-of-box experience self.ws.emit(Message("mycroft.mic.mute")) # Setup handler to unmute mic at the end of on boarding # i.e. after pairing is complete self.ws.once('mycroft.paired', self._handle_pairing_complete) self.speak(mycroft.dialog.get('mycroft.intro')) wait_while_speaking() time.sleep(2) # a pause sounds better than just jumping in # Kick off wifi-setup automatically data = {'allow_timeout': False, 'lang': self.lang} self.ws.emit(Message('system.wifi.setup', data)) def _hack_check_for_duplicates(self): # TEMPORARY HACK: Look for multiple instance of the # mycroft-speech-client and/or mycroft-skills services, which could # happen when upgrading a shipping Mark 1 from release 0.8.17 or # before. When found, force the unit to reboot. import psutil LOG.info("Hack to check for duplicate service instances") count_instances = 0 needs_reboot = False for process in psutil.process_iter(): if process.cmdline() == [ 'python2.7', '/usr/local/bin/mycroft-speech-client' ]: count_instances += 1 if (count_instances > 1): LOG.info("Duplicate mycroft-speech-client found") needs_reboot = True count_instances = 0 for process in psutil.process_iter(): if process.cmdline() == [ 'python2.7', '/usr/local/bin/mycroft-skills' ]: count_instances += 1 if (count_instances > 1): LOG.info("Duplicate mycroft-skills found") needs_reboot = True if needs_reboot: LOG.info("Hack reboot...") self.reader.process("unit.reboot") self.ws.emit(Message("enclosure.eyes.spin")) self.ws.emit(Message("enclosure.mouth.reset"))
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): self.ws = WebsocketClient() ConfigurationManager.init(self.ws) self.config = ConfigurationManager.get().get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws) self.writer = EnclosureWriter(self.serial, self.ws) self.writer.write("system.version") self.ws.on("enclosure.start", self.start) self.started = False Timer(5, self.stop).start() # WHY? This at least # needs an explanation, this is non-obvious behavior def start(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.started = True def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url(url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + self.port) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def stop(self): if not self.started: self.writer.stop() self.reader.stop() self.serial.close() self.ws.close()
class ResponderBackend(object): """ Base class for all responder implementations. set response handlers for specific message_types """ def __init__(self, name=None, emitter=None, logger=None): """ initialize responder args: name(str): name identifier emitter (WebsocketClient): mycroft messagebus websocket logger (logger) : custom logger if desired """ if name is None: self.name = "ResponderBackend" else: self.name = name if emitter is None: self.emitter = WebsocketClient() def connect(): self.emitter.run_forever() ws_thread = Thread(target=connect) ws_thread.setDaemon(True) ws_thread.start() else: self.emitter = emitter self.response_type = "default.reply" if self.responder: self.responder.shutdown() self.responder = None self.callback = None if logger is None: self.logger = LOG else: self.logger = logger self.config = Configuration.get().get(self.name, {}) self.events = [] def update_response_data(self, response_data=None, response_context=None): """ change the data of the response to be sent when queried Args: message_data (dict): response message data message_context (dict): response message context """ if self.responder is not None: self.responder.update_response(response_data, response_context) def set_response_handler(self, trigger_message, callback, response_data=None, response_context=None): """ prepare responder for sending, register answers args: trigger_message (str): message_type that triggers response callback (function): to execute on message_type response_data (dict) : default data of the response message response_context (dict) : default context of the response message """ # update message context response_context = response_context or {} response_context["source"] = self.name response_context["triggered_by"] = trigger_message # generate reply message if ".request" in trigger_message: self.response_type = trigger_message.replace(".request", ".reply") else: self.response_type = trigger_message + ".reply" self.responder = BusResponder(self.emitter, self.response_type, response_data, response_context, []) self.callback = callback self.emitter.on(trigger_message, self._respond) self.events.append(trigger_message) def _respond(self, message): """ on query execute callback and send a response args: message (Message) : query message """ try: if self.callback: self.callback(message) except Exception as e: self.logger.error(e) self.responder.respond(message) def shutdown(self): """ remove all listeners """ for event in self.events: self.emitter.remove(event, self._respond) if self.responder: self.responder.shutdown() self.responder = None
class Enclosure(object): """ Serves as a communication interface between Arduino and Mycroft Core. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ _last_internet_notification = 0 def __init__(self): self.ws = WebsocketClient() ConfigurationManager.init(self.ws) self.config = ConfigurationManager.get().get("enclosure") self.__init_serial() self.reader = EnclosureReader(self.serial, self.ws) self.writer = EnclosureWriter(self.serial, self.ws) # Send a message to the Arduino across the serial line asking # for a reply with version info. self.writer.write("system.version") # When the Arduino responds, it will generate this message self.ws.on("enclosure.started", self.on_arduino_responded) self.arduino_responded = False # Start a 5 second timer. If the serial port hasn't received # any acknowledgement of the "system.version" within those # 5 seconds, assume there is nothing on the other end (e.g. # we aren't running a Mark 1 with an Arduino) Timer(5, self.check_for_response).start() # Notifications from mycroft-core self.ws.on("enclosure.notify.no_internet", self.on_no_internet) def on_arduino_responded(self, event=None): self.eyes = EnclosureEyes(self.ws, self.writer) self.mouth = EnclosureMouth(self.ws, self.writer) self.system = EnclosureArduino(self.ws, self.writer) self.weather = EnclosureWeather(self.ws, self.writer) self.__register_events() self.__reset() self.arduino_responded = True # verify internet connection and prompt user on bootup if needed if not connected(): # We delay this for several seconds to ensure that the other # clients are up and connected to the messagebus in order to # receive the "speak". This was sometimes happening too # quickly and the user wasn't notified what to do. Timer(5, self.on_no_internet).start() def on_no_internet(self, event=None): if connected(): # One last check to see if connection was established return if time.time()-Enclosure._last_internet_notification < 30: # don't bother the user with multiple notifications with 30 secs return Enclosure._last_internet_notification = time.time() # TODO: This should go into EnclosureMark1 subclass of Enclosure. # Handle the translation within that code. self.ws.emit(Message("speak", { 'utterance': "This device is not connected to the Internet. " "Either plug in a network cable or hold the button " "on top for two seconds, then select wifi from the " "menu"})) def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: " + self.port) raise def __register_events(self): self.ws.on('enclosure.mouth.events.activate', self.__register_mouth_events) self.ws.on('enclosure.mouth.events.deactivate', self.__remove_mouth_events) self.ws.on('enclosure.reset', self.__reset) self.__register_mouth_events() def __register_mouth_events(self, event=None): self.ws.on('recognizer_loop:record_begin', self.mouth.listen) self.ws.on('recognizer_loop:record_end', self.mouth.reset) self.ws.on('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self, event=None): self.ws.remove('recognizer_loop:record_begin', self.mouth.listen) self.ws.remove('recognizer_loop:record_end', self.mouth.reset) self.ws.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.ws.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def speak(self, text): self.ws.emit(Message("speak", {'utterance': text})) def run(self): try: self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop() def check_for_response(self): if not self.arduino_responded: # There is nothing on the other end of the serial port # close these serial-port readers and this process self.writer.stop() self.reader.stop() self.serial.close() self.ws.close()
class 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()
class Enclosure: """ Serves as a communication interface between Arduino and Mycroft Core. This interface is currently designed to shut itself down when run on a Raspberry Pi that is not connected to a Mycroft Mark 1 board on the serial port. So it can safely be started on any Raspberry Pi. ``Enclosure`` initializes and aggregates all enclosures implementation. E.g. ``EnclosureEyes``, ``EnclosureMouth`` and ``EnclosureArduino`` It also listens to the basis events in order to perform those core actions on the unit. E.g. Start and Stop talk animation """ def __init__(self): resetSelf = False try: self.config = ConfigurationManager.get().get("enclosure") platform = self.config.get('platform') if platform is None: # Use the serial port to check if this is a Mycroft # Mark 1 unit. platform = self.detect_platform() if platform == 'unknown': # Since this is a semi-permanent detection, be # certain! Sometimes noise on the serial line # causes a faulty mis-detection on a real # Mycroft Mark 1 platform = self.detect_platform() ConfigurationManager.set('enclosure', 'platform', platform) # After a platform detection, the system is usually # already active, so the message from the loop # has already gone by on the messagebus. So self reset. resetSelf = True except Exception as e: self.disconnect() raise Exception("Exception: Unable to determine platform\n" + str(e)) LOGGER.info("Platform = '" + platform + "'") if platform == "mycroft_mark_1": # We are a mycroft_mark_1 unit, start up the # enclosure client to communicate over the # serial port self.__init_serial() else: self.disconnect() raise Exception("Exception: Not a Mycroft Mark 1, shutting down") self.client = WebsocketClient() self.reader = EnclosureReader(self.serial, self.client) self.writer = EnclosureWriter(self.serial, self.client) # Create helpers to handle the various parts of the enclosure self.eyes = EnclosureEyes(self.client, self.writer) self.mouth = EnclosureMouth(self.client, self.writer) self.system = EnclosureArduino(self.client, self.writer) # TODO: Remove EnclosureWeather once the Skill can send images # directly to the EnclosureAPI. self.weather = EnclosureWeather(self.client, self.writer) self.__register_events() if resetSelf: self.__handle_reset(None) def detect_platform(self): LOGGER.info("Auto-detecting platform") try: self.__init_serial() # Thoroughly flush the serial port. Since this happens # right after boot, sometimes there is junk on the serial # port line from electrical noise on a Pi. time.sleep(3) self.serial.flushInput() self.serial.flushOutput() # Write "system.ping" self.serial.write("system.ping") time.sleep(1) # Now check to see if we got a response from the ping command # command we just sent. Remember, there might not be a Mark 1 # Arduino on the other side of the serial port. data = self.serial.readline() LOGGER.info("Serial response: '" + data + "'") # A Mycroft Arduino echos the output to the serial line # prefixed by "Command: ". This assumes only a Mycroft # Arduino responds to this, which is probably a dubious # assumption, but a decent enough auto-detect for now. if "Command: system.ping" in data: # We have a Mycroft Mark 1! # The Arduino returns a version number in the response # with recent builds, but not with older builds. # Empty the serial queue of all responses to the ping. time.sleep(0.5) self.serial.flushInput() return "mycroft_mark_1" except: pass # Likely running on a generic Raspberry Pi or Linux desktop return "unknown" def setup(self): must_upload = self.config.get('must_upload') if must_upload is not None and str2bool(must_upload): ConfigurationManager.set('enclosure', 'must_upload', False) time.sleep(5) self.client.emit(Message("speak", metadata={ 'utterance': "I am currently uploading to the arduino."})) self.client.emit(Message("speak", metadata={ 'utterance': "I will be finished in just a moment."})) self.upload_hex() self.client.emit(Message("speak", metadata={ 'utterance': "Arduino programing complete."})) must_start_test = self.config.get('must_start_test') if must_start_test is not None and str2bool(must_start_test): ConfigurationManager.set('enclosure', 'must_start_test', False) time.sleep(0.5) # Ensure arduino has booted self.client.emit(Message("speak", metadata={ 'utterance': "Begining hardware self test."})) self.writer.write("test.begin") @staticmethod def upload_hex(): old_path = os.getcwd() try: os.chdir('/opt/enclosure/') subprocess.check_call('./upload.sh') finally: os.chdir(old_path) def __init_serial(self): if getattr(self, 'serial', None) is not None: return # already initialized LOGGER.info("Opening serial port") try: self.port = self.config.get("port") self.rate = int(self.config.get("rate")) self.timeout = int(self.config.get("timeout")) self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOGGER.info( "Connected to: " + self.port + " rate: " + str(self.rate) + " timeout: " + str(self.timeout)) except: LOGGER.error( "It is not possible to connect to serial port: " + self.port) raise def __register_events(self): self.client.on('mycroft.paired', self.__update_events) self.client.on('enclosure.reset', self.__handle_reset) self.client.on('enclosure.mouth.listeners', self.__mouth_listeners) self.__register_mouth_events() def __mouth_listeners(self, event=None): if event and event.metadata: active = event.metadata['active'] if active: self.__register_mouth_events() else: self.__remove_mouth_events() def __register_mouth_events(self): self.client.on('recognizer_loop:record_begin', self.mouth.listen) self.client.on('recognizer_loop:record_end', self.mouth.reset) self.client.on('recognizer_loop:audio_output_start', self.mouth.talk) self.client.on('recognizer_loop:audio_output_end', self.mouth.reset) def __remove_mouth_events(self): self.client.remove('recognizer_loop:record_begin', self.mouth.listen) self.client.remove('recognizer_loop:record_end', self.mouth.reset) self.client.remove('recognizer_loop:audio_output_start', self.mouth.talk) self.client.remove('recognizer_loop:audio_output_end', self.mouth.reset) def __handle_reset(self, event=None): # Reset both the mouth and the eye elements to indicate the unit is # ready for input. self.writer.write("eyes.reset") self.writer.write("mouth.reset") def __update_events(self, event=None): if event and event.metadata: if event.metadata.get('paired', False): self.__register_mouth_events() else: self.__remove_mouth_events() def run(self): try: self.client.run_forever() except Exception as e: LOGGER.error("Client error: {0}".format(e)) self.stop() def disconnect(self): if getattr(self, 'serial', None) is not None: self.serial.close() self.serial = None def stop(self): self.writer.stop() self.reader.stop() self.disconnect()
class 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))
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