class PairingSkill(MycroftSkill): def __init__(self): super(PairingSkill, self).__init__("PairingSkill") self.api = DeviceApi() self.data = None self.state = str(uuid4()) self.delay = 10 self.activator = None # TODO: Add translation support self.nato_dict = { 'A': "'A' as in Apple", 'B': "'B' as in Bravo", 'C': "'C' as in Charlie", 'D': "'D' as in Delta", 'E': "'E' as in Echo", 'F': "'F' as in Fox trot", 'G': "'G' as in Golf", 'H': "'H' as in Hotel", 'I': "'I' as in India", 'J': "'J' as in Juliet", 'K': "'K' as in Kilogram", 'L': "'L' as in London", 'M': "'M' as in Mike", 'N': "'N' as in November", 'O': "'O' as in Oscar", 'P': "'P' as in Paul", 'Q': "'Q' as in Quebec", 'R': "'R' as in Romeo", 'S': "'S' as in Sierra", 'T': "'T' as in Tango", 'U': "'U' as in Uniform", 'V': "'V' as in Victor", 'W': "'W' as in Whiskey", 'X': "'X' as in X-Ray", 'Y': "'Y' as in Yankee", 'Z': "'Z' as in Zebra", '1': 'One', '2': 'Two', '3': 'Three', '4': 'Four', '5': 'Five', '6': 'Six', '7': 'Seven', '8': 'Eight', '9': 'Nine', '0': 'Zero' } def initialize(self): intent = IntentBuilder("PairingIntent") \ .require("PairingKeyword").require("DeviceKeyword").build() self.register_intent(intent, self.handle_pairing) self.emitter.on("mycroft.not.paired", self.not_paired) def not_paired(self, message): self.speak_dialog("pairing.not.paired") self.handle_pairing() def handle_pairing(self, message=None): if self.is_paired(): self.speak_dialog("pairing.paired") elif self.data: self.speak_code() else: self.data = self.api.get_code(self.state) self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text(self.data.get("code")) self.speak_code() self.__create_activator() def activate(self): try: token = self.data.get("token") login = self.api.activate(self.state, token) self.enclosure.activate_mouth_events() self.speak_dialog("pairing.paired") IdentityManager.save(login) self.emitter.emit(Message("mycroft.paired", login)) except: self.data["expiration"] -= self.delay if self.data.get("expiration") <= 0: self.data = None self.handle_pairing() else: self.__create_activator() def __create_activator(self): self.activator = Timer(self.delay, self.activate) self.activator.daemon = True self.activator.start() def is_paired(self): try: device = self.api.find() except: device = None return device is not None def speak_code(self): code = self.data.get("code") self.log.info("Pairing code: " + code) data = {"code": '. '.join(map(self.nato_dict.get, code))} self.speak_dialog("pairing.code", data) def stop(self): pass def shutdown(self): super(PairingSkill, self).shutdown() if self.activator: self.activator.cancel()
class PairingSkill(MycroftSkill): poll_frequency = 10 # secs between checking server for activation def __init__(self): super(PairingSkill, self).__init__("PairingSkill") self.api = DeviceApi() self.data = None self.time_code_expires = None self.state = str(uuid4()) self.activator = None self.activator_lock = Lock() self.activator_cancelled = False self.counter_lock = Lock() self.count = -1 # for repeating pairing code. -1 = not running self.nato_dict = None self.mycroft_ready = False self.pair_dialog_lock = Lock() self.paired_dialog = 'pairing.paired' self.pairing_performed = False self.num_failed_codes = 0 def initialize(self): self.add_event("mycroft.not.paired", self.not_paired) self.nato_dict = self.translate_namedvalues('codes') # If the device isn't paired catch mycroft.ready to report # that the device is ready for use. # This assumes that the pairing skill is loaded as a priority skill # before the rest of the skills are loaded. if not is_paired(): self.add_event("mycroft.ready", self.handle_mycroft_ready) platform = self.config_core['enclosure'].get('platform', 'unknown') if platform in PLATFORMS_WITH_BUTTON: self.paired_dialog = 'pairing.paired' else: self.paired_dialog = 'pairing.paired.no.button' def handle_mycroft_ready(self, message): """Catch info that skills are loaded and ready.""" with self.pair_dialog_lock: if is_paired() and self.pairing_performed: self.speak_dialog(self.paired_dialog) else: self.mycroft_ready = True def not_paired(self, message): if not message.data.get('quiet', True): self.speak_dialog("pairing.not.paired") self.handle_pairing() @intent_handler(IntentBuilder("PairingIntent") .require("PairingKeyword").require("DeviceKeyword")) def handle_pairing(self, message=None): if check_remote_pairing(ignore_errors=True): # Already paired! Just tell user self.speak_dialog("already.paired") elif not self.data: # Kick off pairing... with self.counter_lock: if self.count > -1: # We snuck in to this handler somehow while the pairing # process is still being setup. Ignore it. self.log.debug("Ignoring call to handle_pairing") return # Not paired or already pairing, so start the process. self.count = 0 self.reload_skill = False # Prevent restart during the process self.log.debug("Kicking off pairing sequence") try: # Obtain a pairing code from the backend self.data = self.api.get_code(self.state) # Keep track of when the code was obtained. The codes expire # after 20 hours. self.time_code_expires = time.monotonic() + 72000 # 20 hours except Exception: time.sleep(10) # Call restart pairing here # Bail out after Five minutes (5 * 6 attempts at 10 seconds # interval) if self.num_failed_codes < 5 * 6: self.num_failed_codes += 1 self.abort_and_restart(quiet=True) else: self.end_pairing('connection.error') self.num_failed_codes = 0 return self.num_failed_codes = 0 # Reset counter on success mycroft.audio.wait_while_speaking() self.gui.show_page("pairing_start.qml", override_idle=True) self.speak_dialog("pairing.intro") self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text("home.mycroft.ai ") # HACK this gives the Mark 1 time to scroll the address and # the user time to browse to the website. # TODO: mouth_text() really should take an optional parameter # to not scroll a second time. time.sleep(7) mycroft.audio.wait_while_speaking() if not self.activator: self.__create_activator() def check_for_activate(self): """Method is called every 10 seconds by Timer. Checks if user has activated the device yet on home.mycroft.ai and if not repeats the pairing code every 60 seconds. """ try: # Attempt to activate. If the user has completed pairing on the, # backend, this will succeed. Otherwise it throws and HTTPError() token = self.data.get("token") login = self.api.activate(self.state, token) # HTTPError() thrown # When we get here, the pairing code has been entered on the # backend and pairing can now be saved. # The following is kinda ugly, but it is really critical that we # get this saved successfully or we need to let the user know that # they have to perform pairing all over again at the website. try: IdentityManager.save(login) except Exception as e: self.log.debug("First save attempt failed: " + repr(e)) time.sleep(2) try: IdentityManager.save(login) except Exception as e2: # Something must be seriously wrong self.log.debug("Second save attempt failed: " + repr(e2)) self.abort_and_restart() if mycroft.audio.is_speaking(): # Assume speaking is the pairing code. Stop TTS of that. mycroft.audio.stop_speaking() self.enclosure.activate_mouth_events() # clears the display # Notify the system it is paired self.gui.show_page("pairing_done.qml", override_idle=False) self.bus.emit(Message("mycroft.paired", login)) self.pairing_performed = True with self.pair_dialog_lock: if self.mycroft_ready: # Tell user they are now paired self.speak_dialog(self.paired_dialog) mycroft.audio.wait_while_speaking() else: self.speak_dialog("wait.for.startup") mycroft.audio.wait_while_speaking() # Un-mute. Would have been muted during onboarding for a new # unit, and not dangerous to do if pairing was started # independently. self.bus.emit(Message("mycroft.mic.unmute", None)) # Send signal to update configuration self.bus.emit(Message("configuration.updated")) # Allow this skill to auto-update again self.reload_skill = True except HTTPError: # speak pairing code every 60th second with self.counter_lock: if self.count == 0: self.speak_code() self.count = (self.count + 1) % 6 if time.monotonic() > self.time_code_expires: # After 20 hours the token times out. Restart # the pairing process. with self.counter_lock: self.count = -1 self.data = None self.handle_pairing() else: # trigger another check in 10 seconds self.__create_activator() except Exception as e: self.log.debug("Unexpected error: " + repr(e)) self.abort_and_restart() def end_pairing(self, error_dialog): """Resets the pairing and don't restart it. Arguments: error_dialog: Reason for the ending of the pairing process. """ self.speak_dialog(error_dialog) self.bus.emit(Message("mycroft.mic.unmute", None)) self.data = None self.count = -1 def abort_and_restart(self, quiet=False): # restart pairing sequence self.log.debug("Aborting Pairing") self.enclosure.activate_mouth_events() if not quiet: self.speak_dialog("unexpected.error.restarting") # Reset state variables for a new pairing session with self.counter_lock: self.count = -1 self.activator = None self.data = None # Clear pairing code info self.log.info("Restarting pairing process") self.bus.emit(Message("mycroft.not.paired", data={'quiet': quiet})) def __create_activator(self): # Create a timer that will poll the backend in 10 seconds to see # if the user has completed the device registration process with self.activator_lock: if not self.activator_cancelled: self.activator = Timer(PairingSkill.poll_frequency, self.check_for_activate) self.activator.daemon = True self.activator.start() def speak_code(self): """Speak pairing code.""" code = self.data.get("code") self.log.info("Pairing code: " + code) data = {"code": '. '.join(map(self.nato_dict.get, code)) + '.'} # Make sure code stays on display self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text(self.data.get("code")) self.gui['code'] = self.data.get("code") self.gui.show_page("pairing.qml", override_idle=True) self.speak_dialog("pairing.code", data) def shutdown(self): with self.activator_lock: self.activator_cancelled = True if self.activator: self.activator.cancel() if self.activator: self.activator.join()
class PairingSkill(MycroftSkill): def __init__(self): super(PairingSkill, self).__init__("PairingSkill") self.api = DeviceApi() self.data = None self.state = str(uuid4()) self.delay = 10 self.activator = None def initialize(self): self.load_data_files(dirname(__file__)) intent = IntentBuilder("PairingIntent") \ .require("PairingKeyword").require("DeviceKeyword").build() self.register_intent(intent, self.handle_pairing) self.emitter.on("mycroft.not.paired", self.not_paired) def not_paired(self, message): self.speak_dialog("pairing.not.paired") self.handle_pairing() def handle_pairing(self, message=None): if self.is_paired(): self.speak_dialog("pairing.paired") elif self.data: self.speak_code() else: self.data = self.api.get_code(self.state) self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text(self.data.get("code")) self.speak_code() self.activator = Timer(self.delay, self.activate) self.activator.start() def activate(self): try: token = self.data.get("token") login = self.api.activate(self.state, token) self.enclosure.activate_mouth_events() self.speak_dialog("pairing.paired") IdentityManager.save(login) self.emitter.emit(Message("mycroft.paired", login)) except: self.data["expiration"] -= self.delay if self.data.get("expiration") <= 0: self.data = None self.handle_pairing() else: self.activator = Timer(self.delay, self.activate) self.activator.start() def is_paired(self): try: device = self.api.find() except: device = None return device is not None def speak_code(self): code = self.data.get("code") self.log.info("Pairing code: " + code) data = {"code": '. '.join(code).replace("0", "zero")} self.speak_dialog("pairing.code", data) def stop(self): pass
class PairingSkill(MycroftSkill): def __init__(self): super(PairingSkill, self).__init__("PairingSkill") self.api = DeviceApi() self.data = None self.last_request = None self.state = str(uuid4()) self.delay = 10 self.expiration = 72000 # 20 hours self.activator = None self.repeater = None # TODO: Add translation support self.nato_dict = {'A': "'A' as in Apple", 'B': "'B' as in Bravo", 'C': "'C' as in Charlie", 'D': "'D' as in Delta", 'E': "'E' as in Echo", 'F': "'F' as in Fox trot", 'G': "'G' as in Golf", 'H': "'H' as in Hotel", 'I': "'I' as in India", 'J': "'J' as in Juliet", 'K': "'K' as in Kilogram", 'L': "'L' as in London", 'M': "'M' as in Mike", 'N': "'N' as in November", 'O': "'O' as in Oscar", 'P': "'P' as in Paul", 'Q': "'Q' as in Quebec", 'R': "'R' as in Romeo", 'S': "'S' as in Sierra", 'T': "'T' as in Tango", 'U': "'U' as in Uniform", 'V': "'V' as in Victor", 'W': "'W' as in Whiskey", 'X': "'X' as in X-Ray", 'Y': "'Y' as in Yankee", 'Z': "'Z' as in Zebra", '1': 'One', '2': 'Two', '3': 'Three', '4': 'Four', '5': 'Five', '6': 'Six', '7': 'Seven', '8': 'Eight', '9': 'Nine', '0': 'Zero'} def initialize(self): intent = IntentBuilder("PairingIntent") \ .require("PairingKeyword").require("DeviceKeyword").build() self.register_intent(intent, self.handle_pairing) self.emitter.on("mycroft.not.paired", self.not_paired) def not_paired(self, message): self.speak_dialog("pairing.not.paired") self.handle_pairing() def handle_pairing(self, message=None): if self.is_paired(): self.speak_dialog("pairing.paired") elif self.data and self.last_request < time.time(): self.speak_code() else: self.last_request = time.time() + self.expiration self.data = self.api.get_code(self.state) self.enclosure.deactivate_mouth_events() # keeps code on the display self.speak_code() if not self.activator: self.__create_activator() def on_activate(self): try: # wait for a signal from the backend that pairing is complete token = self.data.get("token") login = self.api.activate(self.state, token) # shut down thread that repeats the code to the user if self.repeater: self.repeater.cancel() self.repeater = None # is_speaking() and stop_speaking() support is mycroft-core 0.8.16+ try: if mycroft.util.is_speaking(): # Assume speaking is the pairing code. Stop TTS mycroft.util.stop_speaking() except: pass self.enclosure.activate_mouth_events() # clears the display self.speak_dialog("pairing.paired") # wait_while_speaking() support is mycroft-core 0.8.16+ try: mycroft.util.wait_while_speaking() except: pass IdentityManager.save(login) self.emitter.emit(Message("mycroft.paired", login)) # Un-mute. Would have been muted during onboarding for a new # unit, and not dangerous to do if pairing was started # independently. self.emitter.emit(Message("mycroft.mic.unmute", None)) except: if self.last_request < time.time(): self.data = None self.handle_pairing() else: self.__create_activator() def __create_activator(self): self.activator = Timer(self.delay, self.on_activate) self.activator.daemon = True self.activator.start() def is_paired(self): try: device = self.api.get() except: device = None return device is not None def speak_code(self): """ speak code and start repeating it every 60 second. """ if self.repeater: self.repeater.cancel() self.repeater = None self.__speak_code() self.repeater = Timer(60, self.__repeat_code) self.repeater.daemon = True self.repeater.start() def __speak_code(self): """ Speak code. """ code = self.data.get("code") self.log.info("Pairing code: " + code) data = {"code": '. '.join(map(self.nato_dict.get, code))} self.enclosure.mouth_text(self.data.get("code")) self.speak_dialog("pairing.code", data) def __repeat_code(self): """ Timer function to repeat the code every 60 second. """ # if pairing is complete terminate the thread if self.is_paired(): self.repeater = None return # repeat instructions/code every 60 seconds (start to start) self.__speak_code() self.repeater = Timer(60, self.__repeat_code) self.repeater.daemon = True self.repeater.start() def stop(self): pass def shutdown(self): super(PairingSkill, self).shutdown() if self.activator: self.activator.cancel() if self.repeater: self.repeater.cancel()
class PairingSkill(OVOSSkill): poll_frequency = 5 # secs between checking server for activation def __init__(self): super(PairingSkill, self).__init__("PairingSkill") self.reload_skill = False self.api = DeviceApi() self.data = None self.time_code_expires = None self.state = str(uuid4()) self.activator = None self.activator_lock = Lock() self.activator_cancelled = False self.counter_lock = Lock() self.count = -1 # for repeating pairing code. -1 = not running self.nato_dict = None self.mycroft_ready = False self.num_failed_codes = 0 self.in_pairing = False self.initial_stt = self.config_core["stt"]["module"] self.using_mock = self.config_core["server"][ "url"] != "https://api.mycroft.ai" # startup def initialize(self): # specific distros can override this if "pairing_url" not in self.settings: self.settings["pairing_url"] = "home.mycroft.ai" if "color" not in self.settings: self.settings["color"] = "#FF0000" if not is_paired(): # If the device isn't paired catch mycroft.ready to report # that the device is ready for use. # This assumes that the pairing skill is loaded as a priority skill # before the rest of the skills are loaded. self.add_event("mycroft.ready", self.handle_mycroft_ready) self.in_pairing = True self.make_active() # to enable converse # show loading screen once wifi setup ends if not connected(): self.bus.once("ovos.wifi.setup.completed", self.show_loading_screen) else: # this is usually the first skill to load # ASSUMPTION: is the first skill in priority list self.show_loading_screen() self.add_event("mycroft.not.paired", self.not_paired) # events for GUI interaction self.gui.register_handler("mycroft.device.set.backend", self.handle_backend_selected_event) self.gui.register_handler("mycroft.device.confirm.backend", self.handle_backend_confirmation_event) self.gui.register_handler("mycroft.return.select.backend", self.handle_return_event) self.gui.register_handler("mycroft.device.confirm.stt", self.select_stt) self.gui.register_handler("mycroft.device.confirm.tts", self.select_tts) self.nato_dict = self.translate_namedvalues('codes') def show_loading_screen(self, message=None): self.handle_display_manager("LoadingScreen") def send_stop_signal(self, stop_event=None, should_sleep=True): # TODO move this one into default OVOSkill class # stop the previous event execution if stop_event: self.bus.emit(Message(stop_event)) # stop TTS self.bus.emit(Message("mycroft.audio.speech.stop")) if should_sleep: # STT might continue recording and screw up the next get_response # TODO make mycroft-core allow aborting recording in a sane way self.bus.emit(Message('mycroft.mic.mute')) sleep(0.5) # if TTS had not yet started self.bus.emit(Message("mycroft.audio.speech.stop")) sleep( 1.5) # the silence from muting should make STT stop recording self.bus.emit(Message('mycroft.mic.unmute')) def handle_intent_aborted(self): self.log.info("killing all dialogs") def not_paired(self, message): if not message.data.get('quiet', True): self.speak_dialog("pairing.not.paired") self.handle_pairing() def handle_mycroft_ready(self, message): """Catch info that skills are loaded and ready.""" self.mycroft_ready = True self.gui.remove_page("ProcessLoader.qml") self.bus.emit(Message("mycroft.gui.screen.close", {"skill_id": self.skill_id})) # Tell OVOS-GUI to finally collect resting screens self.bus.emit(Message("ovos.pairing.process.completed")) # voice events def converse(self, message): if self.in_pairing: # capture all utterances until paired # prompts from this skill are handled with get_response return True return False @intent_handler(IntentBuilder("PairingIntent") .require("PairingKeyword").require("DeviceKeyword")) def handle_pairing(self, message=None): self.in_pairing = True if self.using_mock: # user triggered intent, wants to enable pairing self.select_selene() elif check_remote_pairing(ignore_errors=True): # Already paired! Just tell user self.speak_dialog("already.paired") elif not self.data: self.handle_backend_menu() # config handling def change_to_mimic(self): conf = LocalConf(USER_CONFIG) conf["tts"] = { "module": "mimic", "mimic": { "voice": "ap", } } conf.store() self.bus.emit(Message("configuration.patch", {"config": conf})) def change_to_mimic2(self): conf = LocalConf(USER_CONFIG) conf["tts"] = { "module": "mimic2" } conf.store() self.bus.emit(Message("configuration.patch", {"config": conf})) def change_to_larynx(self): conf = LocalConf(USER_CONFIG) conf["tts"] = { "module": "neon-tts-plugin-larynx-server", "neon-tts-plugin-larynx-server": { "host": "http://tts.neon.ai", "voice": "mary_ann", "vocoder": "hifi_gan/vctk_small" } } conf.store() self.bus.emit(Message("configuration.patch", {"config": conf})) def change_to_pico(self): conf = LocalConf(USER_CONFIG) conf["tts"] = { "module": "ovos-tts-plugin-pico" } conf.store() self.bus.emit(Message("configuration.patch", {"config": conf})) def change_to_chromium(self): conf = LocalConf(USER_CONFIG) conf["stt"] = { "module": "ovos-stt-plugin-chromium" } conf.store() self.bus.emit(Message("configuration.patch", {"config": conf})) def change_to_kaldi(self): conf = LocalConf(USER_CONFIG) conf["stt"] = { "module": "ovos-stt-plugin-vosk-streaming", "ovos-stt-plugin-vosk-streaming": { "model": expanduser( "~/.local/share/vosk/vosk-model-small-en-us-0.15") } } conf.store() self.bus.emit(Message("configuration.patch", {"config": conf})) def enable_selene(self): config = { "stt": {"module": "mycroft"}, "server": { "url": "https://api.mycroft.ai", "version": "v1" }, "listener": { "wake_word_upload": { "url": "https://training.mycroft.ai/precise/upload" } } } conf = LocalConf(USER_CONFIG) conf.update(config) conf.store() self.using_mock = False self.bus.emit(Message("configuration.patch", {"config": config})) def enable_mock(self): url = "http://0.0.0.0:{p}".format(p=CONFIGURATION["backend_port"]) version = CONFIGURATION["api_version"] config = { "server": { "url": url, "version": version }, # no web ui to set location, best guess from ip address # should get at least timezone right "location": ip_geolocate("0.0.0.0"), "listener": { "wake_word_upload": { "url": "http://0.0.0.0:{p}/precise/upload".format( p=CONFIGURATION["backend_port"]) } } } conf = LocalConf(USER_CONFIG) conf.update(config) conf.store() self.using_mock = True self.bus.emit(Message("configuration.patch", {"config": config})) # Pairing GUI events #### Backend selection menu @killable_event(msg="pairing.backend.menu.stop") def handle_backend_menu(self): self.send_stop_signal("pairing.confirmation.stop") self.handle_display_manager("BackendSelect") self.speak_dialog("select_backend_gui") def handle_backend_selected_event(self, message): self.send_stop_signal("pairing.backend.menu.stop", should_sleep=False) self.handle_backend_confirmation(message.data["backend"]) def handle_return_event(self, message): self.send_stop_signal("pairing.confirmation.stop", should_sleep=False) page = message.data.get("page", "") self.handle_backend_menu() ### Backend confirmation @killable_event(msg="pairing.confirmation.stop", callback=handle_intent_aborted) def handle_backend_confirmation(self, selection): if selection == "selene": self.handle_display_manager("BackendMycroft") self.speak_dialog("selected_mycroft_backend_gui") elif selection == "local": self.handle_display_manager("BackendLocal") self.speak_dialog("selected_local_backend_gui") def handle_backend_confirmation_event(self, message): self.send_stop_signal("pairing.confirmation.stop") if message.data["backend"] == "local": self.select_local() else: self.select_selene() def select_selene(self): # selene selected if self.using_mock: self.enable_selene() self.data = None # TODO needs to restart, user wants to change back to selene # eg, local was selected and at some point user said # "pair my device" if check_remote_pairing(ignore_errors=True): # Already paired! Just tell user self.speak_dialog("already.paired") self.in_pairing = False elif not self.data: # continue to normal pairing process self.kickoff_pairing() def select_local(self, message=None): # mock backend selected self.data = None self.handle_stt_menu() ### STT selection @killable_event(msg="pairing.stt.menu.stop", callback=handle_intent_aborted) def handle_stt_menu(self): self.handle_display_manager("BackendLocalSTT") self.send_stop_signal("pairing.confirmation.stop") self.speak_dialog("select_mycroft_stt_gui") def select_stt(self, message): selection = message.data["engine"] self.send_stop_signal("pairing.stt.menu.stop") if selection == "google": self.change_to_chromium() elif selection == "kaldi": self.change_to_kaldi() if not self.using_mock: self.enable_mock() # create pairing file with dummy data login = {"uuid": self.state, "access": "OVOSdbF1wJ4jA5lN6x6qmVk_QvJPqBQZTUJQm7fYzkDyY_Y=", "refresh": "OVOS66c5SpAiSpXbpHlq9HNGl1vsw_srX49t5tCv88JkhuE=", "expires_at": time.time() + 999999} IdentityManager.save(login) self.handle_tts_menu() ### TTS selection @killable_event(msg="pairing.tts.menu.stop", callback=handle_intent_aborted) def handle_tts_menu(self): self.handle_display_manager("BackendLocalTTS") self.send_stop_signal("pairing.stt.menu.stop") self.speak_dialog("select_mycroft_tts_gui") def select_tts(self, message): selection = message.data["engine"] self.send_stop_signal() if selection == "mimic": self.change_to_mimic() elif selection == "mimic2": self.change_to_mimic2() elif selection == "pico": self.change_to_pico() elif selection == "larynx": self.change_to_larynx() self.handle_display_manager("BackendLocalRestart") self.in_pairing = False time.sleep(5) system_reboot() # TODO no need for full restart #subprocess.call("sudo systemctl restart mycroft-audio", shell=True) #subprocess.call("sudo systemctl restart mycroft-voice", shell=True) #subprocess.call("sudo systemctl restart mycroft-skills", shell=True) # pairing def kickoff_pairing(self): # Kick off pairing... with self.counter_lock: if self.count > -1: # We snuck in to this handler somehow while the pairing # process is still being setup. Ignore it. self.log.debug("Ignoring call to handle_pairing") return # Not paired or already pairing, so start the process. self.count = 0 self.log.debug("Kicking off pairing sequence") try: # Obtain a pairing code from the backend self.data = self.api.get_code(self.state) # Keep track of when the code was obtained. The codes expire # after 20 hours. self.time_code_expires = time.monotonic() + 72000 # 20 hours except Exception: time.sleep(10) # Call restart pairing here # Bail out after Five minutes (5 * 6 attempts at 10 seconds # interval) if self.num_failed_codes < 5 * 6: self.num_failed_codes += 1 self.abort_and_restart(quiet=True) else: self.end_pairing('connection.error') self.num_failed_codes = 0 return self.num_failed_codes = 0 # Reset counter on success mycroft.audio.wait_while_speaking() self.show_pairing_start() self.speak_dialog("pairing.intro") # HACK this gives the Mark 1 time to scroll the address and # the user time to browse to the website. # TODO: mouth_text() really should take an optional parameter # to not scroll a second time. time.sleep(7) mycroft.audio.wait_while_speaking() if not self.activator: self.__create_activator() def check_for_activate(self): """Method is called every 10 seconds by Timer. Checks if user has activated the device yet on home.mycroft.ai and if not repeats the pairing code every 60 seconds. """ try: # Attempt to activate. If the user has completed pairing on the, # backend, this will succeed. Otherwise it throws and HTTPError() token = self.data.get("token") login = self.api.activate(self.state, token) # HTTPError() thrown # When we get here, the pairing code has been entered on the # backend and pairing can now be saved. # The following is kinda ugly, but it is really critical that we # get this saved successfully or we need to let the user know that # they have to perform pairing all over again at the website. try: IdentityManager.save(login) except Exception as e: self.log.debug("First save attempt failed: " + repr(e)) time.sleep(2) try: IdentityManager.save(login) except Exception as e2: # Something must be seriously wrong self.log.debug("Second save attempt failed: " + repr(e2)) self.abort_and_restart() if mycroft.audio.is_speaking(): # Assume speaking is the pairing code. Stop TTS of that. mycroft.audio.stop_speaking() self.show_pairing_success() self.bus.emit(Message("mycroft.paired", login)) if self.mycroft_ready: # Tell user they are now paired self.speak_dialog("pairing.paired", wait=True) # Un-mute. Would have been muted during onboarding for a new # unit, and not dangerous to do if pairing was started # independently. self.bus.emit(Message("mycroft.mic.unmute", None)) # Send signal to update configuration self.bus.emit(Message("configuration.updated")) except HTTPError: # speak pairing code every 60th second with self.counter_lock: if self.count == 0: self.speak_code() self.count = (self.count + 1) % 6 if time.monotonic() > self.time_code_expires: # After 20 hours the token times out. Restart # the pairing process. with self.counter_lock: self.count = -1 self.data = None self.handle_pairing() else: # trigger another check in 10 seconds self.__create_activator() except Exception as e: self.log.debug("Unexpected error: " + repr(e)) self.abort_and_restart() def end_pairing(self, error_dialog): """Resets the pairing and don't restart it. Arguments: error_dialog: Reason for the ending of the pairing process. """ self.speak_dialog(error_dialog) self.bus.emit(Message("mycroft.mic.unmute", None)) self.data = None self.count = -1 self.in_pairing = False def abort_and_restart(self, quiet=False): # restart pairing sequence self.log.debug("Aborting Pairing") self.enclosure.activate_mouth_events() if not quiet: self.speak_dialog("unexpected.error.restarting") # Reset state variables for a new pairing session with self.counter_lock: self.count = -1 self.activator = None self.data = None # Clear pairing code info self.log.info("Restarting pairing process") self.show_pairing_fail() self.bus.emit(Message("mycroft.not.paired", data={'quiet': quiet})) def __create_activator(self): # Create a timer that will poll the backend in 10 seconds to see # if the user has completed the device registration process with self.activator_lock: if not self.activator_cancelled: self.activator = Timer(PairingSkill.poll_frequency, self.check_for_activate) self.activator.daemon = True self.activator.start() def speak_code(self): """Speak pairing code.""" code = self.data.get("code") self.log.info("Pairing code: " + code) data = {"code": '. '.join(map(self.nato_dict.get, code)) + '.'} self.show_pairing(self.data.get("code")) self.speak_dialog("pairing.code", data) # GUI def handle_display_manager(self, state): self.gui["state"] = state self.gui.show_page( "ProcessLoader.qml", override_idle=True, override_animations=True) def show_pairing_start(self): # Make sure code stays on display self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text(self.settings["pairing_url"] + " ") self.handle_display_manager("PairingStart") # self.gui.show_page("pairing_start.qml", override_idle=True, # override_animations=True) def show_pairing(self, code): # self.gui.remove_page("pairing_start.qml") self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text(code) self.gui["txtcolor"] = self.settings["color"] self.gui["backendurl"] = self.settings["pairing_url"] self.gui["code"] = code self.handle_display_manager("Pairing") # self.gui.show_page("pairing.qml", override_idle=True, # override_animations=True) def show_pairing_success(self): self.enclosure.activate_mouth_events() # clears the display # self.gui.remove_page("pairing.qml") self.gui["status"] = "Success" self.gui["label"] = "Device Paired" self.gui["bgColor"] = "#40DBB0" # self.gui.show_page("status.qml", override_idle=True, # override_animations=True) self.handle_display_manager("Status") # allow GUI to linger around for a bit sleep(5) # self.gui.remove_page("status.qml") self.handle_display_manager("InstallingSkills") def show_pairing_fail(self): self.gui.release() self.gui["status"] = "Failed" self.gui["label"] = "Pairing Failed" self.gui["bgColor"] = "#FF0000" self.handle_display_manager("Status") sleep(5) def shutdown(self): with self.activator_lock: self.activator_cancelled = True if self.activator: self.activator.cancel() if self.activator: self.activator.join()
class PairingSkill(MycroftSkill): poll_frequency = 10 # secs between checking server for activation def __init__(self): super(PairingSkill, self).__init__("PairingSkill") self.api = DeviceApi() self.data = None self.time_code_expires = None self.state = str(uuid4()) self.activator = None self.activator_lock = Lock() self.activator_cancelled = False self.counter_lock = Lock() self.count = -1 # for repeating pairing code. -1 = not running # TODO:18.02 Add translation support # Can't change before then for fear of breaking really old mycroft-core # instances that just came up on wifi and haven't upgraded code yet. self.nato_dict = { 'A': "'A' as in Apple", 'B': "'B' as in Bravo", 'C': "'C' as in Charlie", 'D': "'D' as in Delta", 'E': "'E' as in Echo", 'F': "'F' as in Fox trot", 'G': "'G' as in Golf", 'H': "'H' as in Hotel", 'I': "'I' as in India", 'J': "'J' as in Juliet", 'K': "'K' as in Kilogram", 'L': "'L' as in London", 'M': "'M' as in Mike", 'N': "'N' as in November", 'O': "'O' as in Oscar", 'P': "'P' as in Paul", 'Q': "'Q' as in Quebec", 'R': "'R' as in Romeo", 'S': "'S' as in Sierra", 'T': "'T' as in Tango", 'U': "'U' as in Uniform", 'V': "'V' as in Victor", 'W': "'W' as in Whiskey", 'X': "'X' as in X-Ray", 'Y': "'Y' as in Yankee", 'Z': "'Z' as in Zebra", '1': 'One', '2': 'Two', '3': 'Three', '4': 'Four', '5': 'Five', '6': 'Six', '7': 'Seven', '8': 'Eight', '9': 'Nine', '0': 'Zero' } def initialize(self): # TODO:18.02 - use decorator intent = IntentBuilder("PairingIntent") \ .require("PairingKeyword").require("DeviceKeyword").build() self.register_intent(intent, self.handle_pairing) self.add_event("mycroft.not.paired", self.not_paired) def not_paired(self, message): self.speak_dialog("pairing.not.paired") self.handle_pairing() def handle_pairing(self, message=None): if self.is_paired(): # Already paired! Just tell user self.speak_dialog("pairing.paired") elif not self.data: # Kick off pairing... with self.counter_lock: if self.count > -1: # We snuck in to this handler somehow while the pairing process # is still being setup. Ignore it. self.log.debug("Ignoring call to handle_pairing") return # Not paired or already pairing, so start the process. self.count = 0 self.reload_skill = False # Prevent restart during the process self.log.debug("Kicking off pairing sequence") try: # Obtain a pairing code from the backend self.data = self.api.get_code(self.state) # Keep track of when the code was obtained. The codes expire # after 20 hours. self.time_code_expires = time.time() + 72000 # 20 hours except Exception as e: self.log.debug("Failed to get pairing code: " + repr(e)) self.speak_dialog('connection.error') self.emitter.emit(Message("mycroft.mic.unmute", None)) with self.counter_lock: self.count = -1 return # wait_while_speaking() support is mycroft-core 0.8.16+ # TODO:18.02 - Remove this try/catch and migrate to the preferred # mycroft.audio.wait_while_speaking try: # This will make sure the user is in 0.8.16+ before continuing # so a < 0.8.16 system will skip writing the URL to the mouth mycroft.util.wait_while_speaking() self.speak_dialog("pairing.intro") self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text("home.mycroft.ai ") # HACK this gives the Mark 1 time to scroll the address and # the user time to browse to the website. # TODO: mouth_text() really should take an optional parameter # to not scroll a second time. time.sleep(7) mycroft.util.wait_while_speaking() except: pass if not self.activator: self.__create_activator() def check_for_activate(self): """ Function called ever 10 seconds by Timer. Checks if user has activated the device yet on home.mycroft.ai and if not repeats the pairing code every 60 seconds. """ try: # Attempt to activate. If the user has completed pairing on the, # backend, this will succeed. Otherwise it throws and HTTPError() token = self.data.get("token") login = self.api.activate(self.state, token) # HTTPError() thrown # When we get here, the pairing code has been entered on the # backend and pairing can now be saved. # The following is kinda ugly, but it is really critical that we get # this saved successfully or we need to let the user know that they # have to perform pairing all over again at the website. try: IdentityManager.save(login) except Exception as e: self.log.debug("First save attempt failed: " + repr(e)) time.sleep(2) try: IdentityManager.save(login) except Exception as e2: # Something must be seriously wrong self.log.debug("Second save attempt failed: " + repr(e2)) self.abort_and_restart() # is_speaking() and stop_speaking() support is mycroft-core 0.8.16+ try: if mycroft.util.is_speaking(): # Assume speaking is the pairing code. Stop TTS of that. mycroft.util.stop_speaking() except: pass self.enclosure.activate_mouth_events() # clears the display # Tell user they are now paired self.speak_dialog("pairing.paired") try: mycroft.util.wait_while_speaking() except: pass # Notify the system it is paired and ready self.emitter.emit(Message("mycroft.paired", login)) # Un-mute. Would have been muted during onboarding for a new # unit, and not dangerous to do if pairing was started # independently. self.emitter.emit(Message("mycroft.mic.unmute", None)) # Send signal to update configuration self.emitter.emit(Message("configuration.updated")) # Allow this skill to auto-update again self.reload_skill = True except HTTPError: # speak pairing code every 60th second with self.counter_lock: if self.count == 0: self.speak_code() self.count = (self.count + 1) % 6 if time.time() > self.time_code_expires: # After 20 hours the token times out. Restart # the pairing process. with self.counter_lock: self.count = -1 self.data = None self.handle_pairing() else: # trigger another check in 10 seconds self.__create_activator() except Exception as e: self.log.debug("Unexpected error: " + repr(e)) self.abort_and_restart() def abort_and_restart(self): # restart pairing sequence self.enclosure.activate_mouth_events() self.speak_dialog("unexpected.error.restarting") self.emitter.emit(Message("mycroft.not.paired")) with self.counter_lock: self.count = -1 self.activator = None def __create_activator(self): # Create a timer that will poll the backend in 10 seconds to see # if the user has completed the device registration process with self.activator_lock: if not self.activator_cancelled: self.activator = Timer(PairingSkill.poll_frequency, self.check_for_activate) self.activator.daemon = True self.activator.start() def is_paired(self): """ Determine if pairing process has completed. """ try: device = self.api.get() except: device = None return device is not None def speak_code(self): """ Speak pairing code. """ code = self.data.get("code") self.log.info("Pairing code: " + code) data = {"code": '. '.join(map(self.nato_dict.get, code))} # Make sure code stays on display self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text(self.data.get("code")) self.speak_dialog("pairing.code", data) def shutdown(self): super(PairingSkill, self).shutdown() with self.activator_lock: self.activator_cancelled = True if self.activator: self.activator.cancel() if self.activator: self.activator.join()