Exemple #1
0
    def download_skills(self, speak=False):
        """ Invoke MSM to install default skills and/or update installed skills

            Args:
                speak (bool, optional): Speak the result? Defaults to False
        """
        # Don't invoke msm if already running
        if exists(MSM_BIN) and self.__msm_lock.acquire():
            try:
                # Invoke the MSM script to do the hard work.
                LOG.debug("==== Invoking Mycroft Skill Manager: " + MSM_BIN)
                p = subprocess.Popen(MSM_BIN + " default",
                                     stderr=subprocess.STDOUT,
                                     stdout=subprocess.PIPE,
                                     shell=True)
                (output, err) = p.communicate()
                res = p.returncode
                # Always set next update to an hour from now if successful
                if res == 0:
                    self.next_download = time.time() + 60 * MINUTES

                    if res == 0 and speak:
                        self.ws.emit(
                            Message(
                                "speak",
                                {'utterance': dialog.get("skills updated")}))
                    return True
                elif not connected():
                    LOG.error('msm failed, network connection not available')
                    if speak:
                        self.ws.emit(
                            Message(
                                "speak", {
                                    'utterance':
                                    dialog.get("not connected to the internet")
                                }))
                    self.next_download = time.time() + 5 * MINUTES
                    return False
                elif res != 0:
                    LOG.error('msm failed with error {}: {}'.format(
                        res, output))
                    if speak:
                        self.ws.emit(
                            Message(
                                "speak", {
                                    'utterance':
                                    dialog.get(
                                        "sorry I couldn't install default skills"
                                    )
                                }))
                    self.next_download = time.time() + 5 * MINUTES
                    return False
            finally:
                self.__msm_lock.release()
        else:
            LOG.error("Unable to invoke Mycroft Skill Manager: " + MSM_BIN)
Exemple #2
0
    def transcribe(self, audio):
        try:
            # Invoke the STT engine on the audio clip
            text = self.stt.execute(audio).lower().strip()
            LOG.debug("STT: " + text)
            return text
        except sr.RequestError as e:
            LOG.error("Could not request Speech Recognition {0}".format(e))
        except ConnectionError as e:
            LOG.error("Connection Error: {0}".format(e))

            self.emitter.emit("recognizer_loop:no_internet")
        except HTTPError as e:
            if e.response.status_code == 401:
                LOG.warning("Access Denied at mycroft.ai")
                return "pair my device"  # phrase to start the pairing process
            else:
                LOG.error(e.__class__.__name__ + ': ' + str(e))
        except RequestException as e:
            LOG.error(e.__class__.__name__ + ': ' + str(e))
        except Exception as e:
            self.emitter.emit('recognizer_loop:speech.recognition.unknown')
            if isinstance(e, IndexError):
                LOG.info('no words were transcribed')
            else:
                LOG.error(e)
            LOG.error("Speech Recognition could not understand audio")
            return None
        if connected():
            dialog_name = 'backend.down'
        else:
            dialog_name = 'not connected to the internet'
        self.emitter.emit('speak', {'utterance': dialog.get(dialog_name)})
Exemple #3
0
    def transcribe(self, audio):
        def send_unknown_intent():
            """ Send message that nothing was transcribed. """
            self.emitter.emit('recognizer_loop:speech.recognition.unknown')

        try:
            # Invoke the STT engine on the audio clip
            text = self.stt.execute(audio)
            if text is not None:
                text = text.lower().strip()
                LOG.debug("STT: " + text)
            else:
                send_unknown_intent()
                LOG.info('no words were transcribed')
            return text
        except sr.RequestError as e:
            LOG.error("Could not request Speech Recognition {0}".format(e))
        except ConnectionError as e:
            LOG.error("Connection Error: {0}".format(e))

            self.emitter.emit("recognizer_loop:no_internet")
        except RequestException as e:
            LOG.error(e.__class__.__name__ + ': ' + str(e))
        except Exception as e:
            send_unknown_intent()
            LOG.error(e)
            LOG.error("Speech Recognition could not understand audio")
            return None

        if connected():
            dialog_name = 'backend.down'
        else:
            dialog_name = 'not connected to the internet'
        self.emitter.emit('speak', {'utterance': dialog.get(dialog_name)})
Exemple #4
0
def handle_complete_intent_failure(event):
    """Extreme backup for answering completely unhandled intent requests."""
    LOG.info("Failed to find intent.")
    data = {'utterance': dialog.get('not.loaded')}
    context = {'client_name': 'mycroft_listener',
               'source': 'audio'}
    bus.emit(Message('speak', data, context))
Exemple #5
0
    def transcribe(self, audio):
        try:
            # Invoke the STT engine on the audio clip
            text = self.stt.execute(audio).lower().strip()
            LOG.debug("STT: " + text)
            return text
        except sr.RequestError as e:
            LOG.error("Could not request Speech Recognition {0}".format(e))
        except ConnectionError as e:
            LOG.error("Connection Error: {0}".format(e))

            self.emitter.emit("recognizer_loop:no_internet")
        except HTTPError as e:
            if e.response.status_code == 401:
                LOG.warning("Access Denied at mycroft.ai")
                return "pair my device"  # phrase to start the pairing process
            else:
                LOG.error(e.__class__.__name__ + ': ' + str(e))
        except RequestException as e:
            LOG.error(e.__class__.__name__ + ': ' + str(e))
        except Exception as e:
            self.emitter.emit('recognizer_loop:speech.recognition.unknown')
            if isinstance(e, IndexError):
                LOG.info('no words were transcribed')
            else:
                LOG.error(e)
            LOG.error("Speech Recognition could not understand audio")
            return None
        if connected():
            dialog_name = 'backend.down'
        else:
            dialog_name = 'not connected to the internet'
        self.emitter.emit('speak', {'utterance': dialog.get(dialog_name)})
Exemple #6
0
    def test_get(self):
        phrase = 'i didn\'t catch that'
        res_file = pathlib.Path('text/en-us/').joinpath(phrase + '.dialog')
        print(res_file)
        resource = resolve_resource_file(str(res_file))
        with open(resource) as f:
            results = [line.strip() for line in f]
        string = get(phrase)
        self.assertIn(string, results)

        # Check that the filename is returned if phrase is missing for lang
        string = get(phrase, lang='ne-ne')
        self.assertEqual(string, phrase)

        # Check that name is retured if phrase is missing
        string = get('testing aardwark')
        self.assertEqual(string, 'testing aardwark')
Exemple #7
0
    def test_get(self):
        phrase = 'i didn\'t catch that'
        res_file = pathlib.Path('text/en-us/').joinpath(phrase + '.dialog')
        print(res_file)
        resource = resolve_resource_file(str(res_file))
        with open(resource) as f:
            results = [line.strip() for line in f]
        string = get(phrase)
        self.assertIn(string, results)

        # Check that the filename is returned if phrase is missing for lang
        string = get(phrase, lang='ne-ne')
        self.assertEqual(string, phrase)

        # Check that name is retured if phrase is missing
        string = get('testing aardwark')
        self.assertEqual(string, 'testing aardwark')
Exemple #8
0
 def on_error(e):
     """Speak and log the error."""
     # Convert "MyFancySkill" to "My Fancy Skill" for speaking
     handler_name = camel_case_split(self.name)
     msg_data = {'skill': handler_name}
     msg = dialog.get('skill.error', self.lang, msg_data)
     self.speak(msg)
     LOG.exception(msg)
     # append exception information in message
     skill_data['exception'] = repr(e)
Exemple #9
0
 def schedule_update_skills(self, message=None):
     """ Schedule a skill update to take place directly. """
     if direct_update_needed():
         # Update skills at next opportunity
         LOG.info('Skills will be updated directly')
         self.schedule_now()
         # Skip the  message when unpaired because the prompt to go
         # to home.mycrof.ai will be displayed by the pairing skill
         if not is_paired():
             self.enclosure.mouth_text(dialog.get("message_updating"))
     else:
         LOG.info('Skills will be updated at a later time')
         self.next_download = time.time() + 60 * MINUTES
Exemple #10
0
 def on_error(e):
     """Speak and log the error."""
     if not isinstance(e, AbortEvent):
         # Convert "MyFancySkill" to "My Fancy Skill" for speaking
         handler_name = camel_case_split(self.name)
         msg_data = {'skill': handler_name}
         msg = dialog.get('skill.error', self.lang, msg_data)
         self.speak(msg)
         LOG.exception(msg)
     else:
         LOG.info("Skill execution aborted")
     # append exception information in message
     skill_data['exception'] = repr(e)
    def _update_system_clock(self):
        """Force a sync of the local clock with the Network Time Protocol.

        The NTP sync is only forced on Raspberry Pi based devices.  The
        assumption being that these devices are only running Mycroft services.
        We don't want to sync the time on a Linux desktop device, for example,
        because it could have a negative impact on other software running on
        that device.
        """
        if self.platform in RASPBERRY_PI_PLATFORMS:
            LOG.info('Updating the system clock via NTP...')
            if self.is_paired:
                # Only display time sync message when paired because the prompt
                # to go to home.mycroft.ai will be displayed by the pairing
                # skill when pairing
                self.enclosure.mouth_text(dialog.get("message_synching.clock"))
            self.bus.wait_for_response(Message('system.ntp.sync'),
                                       'system.ntp.sync.complete', 15)
Exemple #12
0
        def wrapper(message):
            skill_data = {'name': get_handler_name(handler)}
            stopwatch = Stopwatch()
            try:
                message = unmunge_message(message, self.skill_id)
                # Indicate that the skill handler is starting
                if handler_info:
                    # Indicate that the skill handler is starting if requested
                    msg_type = handler_info + '.start'
                    self.bus.emit(message.reply(msg_type, skill_data))

                if once:
                    # Remove registered one-time handler before invoking,
                    # allowing them to re-schedule themselves.
                    self.remove_event(name)

                with stopwatch:
                    if len(signature(handler).parameters) == 0:
                        handler()
                    else:
                        handler(message)
                    self.settings.store()  # Store settings if they've changed

            except Exception as e:
                # Convert "MyFancySkill" to "My Fancy Skill" for speaking
                handler_name = camel_case_split(self.name)
                msg_data = {'skill': handler_name}
                msg = dialog.get('skill.error', self.lang, msg_data)
                self.speak(msg)
                LOG.exception(msg)
                # append exception information in message
                skill_data['exception'] = repr(e)
            finally:
                # Indicate that the skill handler has completed
                if handler_info:
                    msg_type = handler_info + '.complete'
                    self.bus.emit(message.reply(msg_type, skill_data))

                # Send timing metrics
                context = message.context
                if context and 'ident' in context:
                    report_timing(context['ident'], 'skill_handler', stopwatch,
                                  {'handler': handler.__name__})
Exemple #13
0
    def _reboot(self):
        """If the NTP sync skewed system time significantly, reboot.

        If system time moved by over an hour in the NTP sync, force a reboot to
        prevent weird things from occurring due to the 'time warp'.
        """
        LOG.warning(
            'Clock sync altered system time by more than one hour,'
            ' rebooting...'
        )
        self._speak_dialog(dialog_id="time.changed.reboot", wait=True)
        # provide visual indicators of the reboot
        self.enclosure.mouth_text(dialog.get("message_rebooting"))
        self.enclosure.eyes_color(70, 65, 69)  # soft gray
        self.enclosure.eyes_spin()
        # give the system time to finish processing enclosure messages
        time.sleep(1.0)
        # reboot
        self.bus.emit(Message("system.reboot"))
Exemple #14
0
        def wrapper(message):
            skill_data = {'name': get_handler_name(handler)}
            stopwatch = Stopwatch()
            try:
                message = unmunge_message(message, self.skill_id)
                # Indicate that the skill handler is starting
                if handler_info:
                    # Indicate that the skill handler is starting if requested
                    msg_type = handler_info + '.start'
                    self.emitter.emit(Message(msg_type, skill_data))

                with stopwatch:
                    is_bound = bool(getattr(handler, 'im_self', None))
                    num_args = len(getargspec(handler).args) - is_bound
                    if num_args == 0:
                        handler()
                    else:
                        handler(message)
                    self.settings.store()  # Store settings if they've changed

            except Exception as e:
                # Convert "MyFancySkill" to "My Fancy Skill" for speaking
                handler_name = re.sub(r"([a-z])([A-Z])", r"\1 \2", self.name)
                msg_data = {'skill': handler_name}
                msg = dialog.get('skill.error', self.lang, msg_data)
                self.speak(msg)
                LOG.exception(msg)
                # append exception information in message
                skill_data['exception'] = e.message
            finally:
                if once:
                    self.remove_event(name)

                # Indicate that the skill handler has completed
                if handler_info:
                    msg_type = handler_info + '.complete'
                    self.emitter.emit(Message(msg_type, skill_data))

                # Send timing metrics
                context = message.context
                if context and 'ident' in context:
                    report_timing(context['ident'], 'skill_handler', stopwatch,
                                  {'handler': handler.__name__})
 def _speak_dialog(self, dialog_id, wait=False):
     data = {'utterance': dialog.get(dialog_id)}
     self.bus.emit(Message("speak", data))
     if wait:
         wait_while_speaking()
 def _display_skill_loading_notification(self):
     """Indicate to the user that skills are being loaded."""
     self.enclosure.eyes_color(189, 183, 107)  # dark khaki
     self.enclosure.mouth_text(dialog.get("message_loading.skills"))
def check_connection():
    """
        Check for network connection. If not paired trigger pairing.
        Runs as a Timer every second until connection is detected.
    """
    if connected():
        enclosure = EnclosureAPI(bus)

        if is_paired():
            # Skip the sync message when unpaired because the prompt to go to
            # home.mycrof.ai will be displayed by the pairing skill
            enclosure.mouth_text(dialog.get("message_synching.clock"))

        # Force a sync of the local clock with the internet
        config = Configuration.get()
        platform = config['enclosure'].get("platform", "unknown")
        if platform in ['mycroft_mark_1', 'picroft']:
            bus.wait_for_response(Message('system.ntp.sync'),
                                  'system.ntp.sync.complete', 15)

        if not is_paired():
            try_update_system(platform)

        # Check if the time skewed significantly.  If so, reboot
        skew = abs((time.monotonic() - start_ticks) -
                   (time.time() - start_clock))
        if skew > 60 * 60:
            # Time moved by over an hour in the NTP sync. Force a reboot to
            # prevent weird things from occcurring due to the 'time warp'.
            #
            data = {'utterance': dialog.get("time.changed.reboot")}
            bus.emit(Message("speak", data))
            wait_while_speaking()

            # provide visual indicators of the reboot
            enclosure.mouth_text(dialog.get("message_rebooting"))
            enclosure.eyes_color(70, 65, 69)  # soft gray
            enclosure.eyes_spin()

            # give the system time to finish processing enclosure messages
            time.sleep(1.0)

            # reboot
            bus.emit(Message("system.reboot"))
            return
        else:
            bus.emit(Message("enclosure.mouth.reset"))
            time.sleep(0.5)

        enclosure.eyes_color(189, 183, 107)  # dark khaki
        enclosure.mouth_text(dialog.get("message_loading.skills"))

        bus.emit(Message('mycroft.internet.connected'))
        # check for pairing, if not automatically start pairing
        try:
            if not is_paired(ignore_errors=False):
                payload = {'utterances': ["pair my device"], 'lang': "en-us"}
                bus.emit(Message("recognizer_loop:utterance", payload))
            else:
                from mycroft.api import DeviceApi
                api = DeviceApi()
                api.update_version()
        except BackendDown:
            data = {'utterance': dialog.get("backend.down")}
            bus.emit(Message("speak", data))
            bus.emit(Message("backend.down"))

    else:
        thread = Timer(1, check_connection)
        thread.daemon = True
        thread.start()
Exemple #18
0
    def download_skills(self, speak=False):
        """ Invoke MSM to install default skills and/or update installed skills

            Args:
                speak (bool, optional): Speak the result? Defaults to False
        """
        if not connected():
            LOG.error('msm failed, network connection not available')
            if speak:
                self.ws.emit(
                    Message("speak", {
                        'utterance':
                        dialog.get("not connected to the internet")
                    }))
            self.next_download = time.time() + 5 * MINUTES
            return False

        installed_skills = self.load_installed_skills()
        default_groups = dict(self.msm.repo.get_default_skill_names())
        default_names = set(
            chain(default_groups['default'],
                  default_groups[self.msm.platform]))

        default_skill_errored = False

        def install_or_update(skill):
            """Install missing defaults and update existing skills"""
            try:
                if skill.is_local:
                    skill.update()
                    if skill.name not in installed_skills:
                        skill.update_deps()
                elif skill.name in default_names:
                    skill.install()
            except Exception:
                if skill.name in default_names:
                    nonlocal default_skill_errored
                    default_skill_errored = True
                raise
            installed_skills.add(skill.name)

        try:
            self.msm.apply(install_or_update, self.msm.list())
        except MsmException as e:
            LOG.error('Failed to update skills: {}'.format(repr(e)))

        self.save_installed_skills(installed_skills)

        if speak:
            data = {'utterance': dialog.get("skills updated")}
            self.ws.emit(Message("speak", data))

        if default_skill_errored:
            self.next_download = time.time() + 5 * MINUTES
            return False

        with open(self.dot_msm, 'a'):
            os.utime(self.dot_msm, None)
        self.next_download = time.time() + self.update_interval

        return True
Exemple #19
0
    def download_skills(self, speak=False):
        """ Invoke MSM to install default skills and/or update installed skills

            Args:
                speak (bool, optional): Speak the result? Defaults to False
        """
        if not connected():
            LOG.error('msm failed, network connection not available')
            if speak:
                self.bus.emit(Message("speak", {
                    'utterance': dialog.get(
                        "not connected to the internet")}))
            self.next_download = time.time() + 5 * MINUTES
            return False

        installed_skills = self.load_installed_skills()
        default_groups = dict(self.msm.repo.get_default_skill_names())
        if self.msm.platform in default_groups:
            platform_groups = default_groups[self.msm.platform]
        else:
            LOG.info('Platform defaults not found, using DEFAULT skills only')
            platform_groups = []
        default_names = set(chain(default_groups['default'], platform_groups))
        default_skill_errored = False

        skills_data = self.load_skills_data()

        def install_or_update(skill):
            """Install missing defaults and update existing skills"""
            if skills_data.get(skill.name, {}).get('beta'):
                skill.sha = 'HEAD'
            if skill.is_local:
                skill.update()
                if skill.name not in installed_skills:
                    skill.update_deps()
            elif skill.name in default_names:
                try:
                    skill.install()
                except Exception:
                    if skill.name in default_names:
                        LOG.warning(
                            'Failed to install default skill: ' + skill.name
                        )
                        nonlocal default_skill_errored
                        default_skill_errored = True
                    raise
            installed_skills.add(skill.name)

        try:
            self.msm.apply(install_or_update, self.msm.list())
        except MsmException as e:
            LOG.error('Failed to update skills: {}'.format(repr(e)))

        for skill_name in installed_skills:
            skills_data.setdefault(skill_name, {})['installed'] = True
        self.write_skills_data(skills_data)
        self.save_installed_skills(installed_skills)

        if speak:
            data = {'utterance': dialog.get("skills updated")}
            self.bus.emit(Message("speak", data))

        if default_skill_errored and self.num_install_retries < 10:
            self.num_install_retries += 1
            self.next_download = time.time() + 5 * MINUTES
            return False
        self.num_install_retries = 0

        with open(self.dot_msm, 'a'):
            os.utime(self.dot_msm, None)
        self.next_download = time.time() + self.update_interval

        return True
    def download_skills(self, speak=False):
        """ Invoke MSM to install default skills and/or update installed skills

            Args:
                speak (bool, optional): Speak the result? Defaults to False
        """
        if not connected():
            LOG.error('msm failed, network connection not available')
            if speak:
                self.bus.emit(
                    Message("speak", {
                        'utterance':
                        dialog.get("not connected to the internet")
                    }))
            self.next_download = time.time() + 5 * MINUTES
            return False

        installed_skills = self.load_installed_skills()
        msm = SkillManager.create_msm()
        with msm.lock, self.thread_lock:
            default_groups = dict(msm.repo.get_default_skill_names())
            if msm.platform in default_groups:
                platform_groups = default_groups[msm.platform]
            else:
                LOG.info('Platform defaults not found, using DEFAULT '
                         'skills only')
                platform_groups = []
            default_names = set(
                chain(default_groups['default'], platform_groups))
            default_skill_errored = False

            def get_skill_data(skill_name):
                """ Get skill data structure from name. """
                for e in msm.skills_data.get('skills', []):
                    if e.get('name') == skill_name:
                        return e
                # if skill isn't in the list return empty structure
                return {}

            def install_or_update(skill):
                """Install missing defaults and update existing skills"""
                if get_skill_data(skill.name).get('beta'):
                    skill.sha = None  # Will update to latest head
                if skill.is_local:
                    skill.update()
                    if skill.name not in installed_skills:
                        skill.update_deps()
                elif skill.name in default_names:
                    try:
                        msm.install(skill, origin='default')
                    except Exception:
                        if skill.name in default_names:
                            LOG.warning('Failed to install default skill: ' +
                                        skill.name)
                            nonlocal default_skill_errored
                            default_skill_errored = True
                        raise
                installed_skills.add(skill.name)

            try:
                msm.apply(install_or_update, msm.list())
                if SkillManager.manifest_upload_allowed and is_paired():
                    try:
                        DeviceApi().upload_skills_data(msm.skills_data)
                    except Exception:
                        LOG.exception('Could not upload skill manifest')

            except MsmException as e:
                LOG.error('Failed to update skills: {}'.format(repr(e)))

        self.save_installed_skills(installed_skills)

        if speak:
            data = {'utterance': dialog.get("skills updated")}
            self.bus.emit(Message("speak", data))

        if default_skill_errored and self.num_install_retries < 10:
            self.num_install_retries += 1
            self.next_download = time.time() + 5 * MINUTES
            return False
        self.num_install_retries = 0

        with open(self.dot_msm, 'a'):
            os.utime(self.dot_msm, None)
        self.next_download = time.time() + self.update_interval

        return True
Exemple #21
0
def handle_complete_intent_failure(event):
    LOG.info("Failed to find intent.")
    data = {'utterance': dialog.get('not.loaded')}
    bus.emit(Message('speak', data))
Exemple #22
0
def check_connection():
    """
        Check for network connection. If not paired trigger pairing.
        Runs as a Timer every second until connection is detected.
    """
    if connected():
        enclosure = EnclosureAPI(bus)

        if is_paired():
            # Skip the sync message when unpaired because the prompt to go to
            # home.mycrof.ai will be displayed by the pairing skill
            enclosure.mouth_text(dialog.get("message_synching.clock"))

        # Force a sync of the local clock with the internet
        config = Configuration.get()
        platform = config['enclosure'].get("platform", "unknown")
        if platform in ['mycroft_mark_1', 'picroft']:
            bus.emit(Message("system.ntp.sync"))
            time.sleep(15)  # TODO: Generate/listen for a message response...

        # Check if the time skewed significantly.  If so, reboot
        skew = abs((time.monotonic() - start_ticks) -
                   (time.time() - start_clock))
        if skew > 60 * 60:
            # Time moved by over an hour in the NTP sync. Force a reboot to
            # prevent weird things from occcurring due to the 'time warp'.
            #
            data = {'utterance': dialog.get("time.changed.reboot")}
            bus.emit(Message("speak", data))
            wait_while_speaking()

            # provide visual indicators of the reboot
            enclosure.mouth_text(dialog.get("message_rebooting"))
            enclosure.eyes_color(70, 65, 69)  # soft gray
            enclosure.eyes_spin()

            # give the system time to finish processing enclosure messages
            time.sleep(1.0)

            # reboot
            bus.emit(Message("system.reboot"))
            return
        else:
            bus.emit(Message("enclosure.mouth.reset"))
            time.sleep(0.5)

        bus.emit(Message('mycroft.internet.connected'))
        # check for pairing, if not automatically start pairing
        try:
            if not is_paired(ignore_errors=False):
                payload = {
                    'utterances': ["pair my device"],
                    'lang': "en-us"
                }
                bus.emit(Message("recognizer_loop:utterance", payload))
            else:
                from mycroft.api import DeviceApi
                api = DeviceApi()
                api.update_version()
        except BackendDown:
            data = {'utterance': dialog.get("backend.down")}
            bus.emit(Message("speak", data))
            bus.emit(Message("backend.down"))

    else:
        thread = Timer(1, check_connection)
        thread.daemon = True
        thread.start()
    def download_skills(self, speak=False):
        """ Invoke MSM to install default skills and/or update installed skills

            Args:
                speak (bool, optional): Speak the result? Defaults to False
        """
        if not connected():
            LOG.error('msm failed, network connection not available')
            if speak:
                self.bus.emit(Message("speak", {
                    'utterance': dialog.get(
                        "not connected to the internet")}))
            self.next_download = time.time() + 5 * MINUTES
            return False

        installed_skills = self.load_installed_skills()
        default_groups = dict(self.msm.repo.get_default_skill_names())
        if self.msm.platform in default_groups:
            platform_groups = default_groups[self.msm.platform]
        else:
            LOG.info('Platform defaults not found, using DEFAULT skills only')
            platform_groups = []
        default_names = set(chain(default_groups['default'], platform_groups))
        default_skill_errored = False

        skills_data = self.load_skills_data()

        new_installs = []
        updated_skills = []

        def install_or_update(skill):
            """Install missing defaults and update existing skills"""
            if skills_data.get(skill.name, {}).get('beta'):
                skill.sha = None  # Will update to latest version
            if skill.is_local:
                skill.update()
                updated_skills.append(skill.name)
                if skill.name not in installed_skills:
                    skill.update_deps()
                    installed_skills.add(skill.name)
            elif skill.name in default_names:
                try:
                    new_installs.append(skill.name)
                    skill.install()
                except Exception:
                    if skill.name in default_names:
                        LOG.warning(
                            'Failed to install default skill: ' + skill.name
                        )
                        nonlocal default_skill_errored
                        default_skill_errored = True
                    raise
                installed_skills.add(skill.name)

        try:
            self.msm.apply(install_or_update, self.msm.list())
        except MsmException as e:
            LOG.error('Failed to update skills: {}'.format(repr(e)))

        for skill_name in new_installs + updated_skills:
            if skill_name not in skills_data or skill_name in new_installs:
                t = time.time() if skill_name in new_installs else 1
                skills_data.setdefault(skill_name, {})['installed'] = t
                skills_data[skill_name]['updated'] = 0

        self.write_skills_data(skills_data)
        self.save_installed_skills(installed_skills)

        if speak:
            data = {'utterance': dialog.get("skills updated")}
            self.bus.emit(Message("speak", data))

        if default_skill_errored and self.num_install_retries < 10:
            self.num_install_retries += 1
            self.next_download = time.time() + 5 * MINUTES
            return False
        self.num_install_retries = 0

        with open(self.dot_msm, 'a'):
            os.utime(self.dot_msm, None)
        self.next_download = time.time() + self.update_interval

        return True
    def download_skills(self, speak=False):
        """ Invoke MSM to install default skills and/or update installed skills

            Args:
                speak (bool, optional): Speak the result? Defaults to False
        """
        if not connected():
            LOG.error('msm failed, network connection not available')
            if speak:
                self.bus.emit(Message("speak", {
                    'utterance': dialog.get(
                        "not connected to the internet")}))
            self.next_download = time.time() + 5 * MINUTES
            return False

        installed_skills = self.load_installed_skills()
        msm = SkillManager.create_msm()
        with msm.lock, self.thread_lock:
            default_groups = dict(msm.repo.get_default_skill_names())
            if msm.platform in default_groups:
                platform_groups = default_groups[msm.platform]
            else:
                LOG.info('Platform defaults not found, using DEFAULT '
                         'skills only')
                platform_groups = []
            default_names = set(chain(default_groups['default'],
                                      platform_groups))
            default_skill_errored = False

            def get_skill_data(skill_name):
                """ Get skill data structure from name. """
                for e in msm.skills_data.get('skills', []):
                    if e.get('name') == skill_name:
                        return e
                # if skill isn't in the list return empty structure
                return {}

            def install_or_update(skill):
                """Install missing defaults and update existing skills"""
                if get_skill_data(skill.name).get('beta'):
                    skill.sha = None  # Will update to latest head
                if skill.is_local:
                    skill.update()
                    if skill.name not in installed_skills:
                        skill.update_deps()
                elif skill.name in default_names:
                    try:
                        msm.install(skill, origin='default')
                    except Exception:
                        if skill.name in default_names:
                            LOG.warning('Failed to install default skill: ' +
                                        skill.name)
                            nonlocal default_skill_errored
                            default_skill_errored = True
                        raise
                installed_skills.add(skill.name)
            try:
                msm.apply(install_or_update, msm.list())
                if SkillManager.manifest_upload_allowed and is_paired():
                    try:
                        DeviceApi().upload_skills_data(msm.skills_data)
                    except Exception:
                        LOG.exception('Could not upload skill manifest')

            except MsmException as e:
                LOG.error('Failed to update skills: {}'.format(repr(e)))

        self.save_installed_skills(installed_skills)

        if speak:
            data = {'utterance': dialog.get("skills updated")}
            self.bus.emit(Message("speak", data))

        if default_skill_errored and self.num_install_retries < 10:
            self.num_install_retries += 1
            self.next_download = time.time() + 5 * MINUTES
            return False
        self.num_install_retries = 0

        with open(self.dot_msm, 'a'):
            os.utime(self.dot_msm, None)
        self.next_download = time.time() + self.update_interval

        return True
def handle_complete_intent_failure(event):
    """Extreme backup for answering completely unhandled intent requests."""
    LOG.info("Failed to find intent.")
    data = {'utterance': dialog.get('not.loaded')}
    bus.emit(Message('speak', data))