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()
예제 #2
0
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()
예제 #3
0
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
예제 #4
0
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()
예제 #5
0
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()
예제 #6
0
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()