def run(self): """Load skills and update periodically from disk and internet.""" self._remove_git_locks() self._connected_event.wait() self._load_on_startup() # Sync backend and skills. if is_paired() and not self.upload_queue.started: self._start_settings_update() # Scan the file folder that contains Skills. If a Skill is updated, # unload the existing version from memory and reload from the disk. while not self._stop_event.is_set(): try: self._reload_modified_skills() self._load_new_skills() self._unload_removed_skills() self._update_skills() if (is_paired() and self.upload_queue.started and len(self.upload_queue) > 0): self.msm.clear_cache() self.skill_updater.post_manifest() self.upload_queue.send() sleep(2) # Pause briefly before beginning next scan except Exception: LOG.exception('Something really unexpected has occured ' 'and the skill manager loop safety harness was ' 'hit.') sleep(30)
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(ws) 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(mycroft.dialog.get("message_synching.clock")) # Force a sync of the local clock with the internet ws.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((monotonic.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'. # ws.emit( Message( "speak", {'utterance': mycroft.dialog.get("time.changed.reboot")})) wait_while_speaking() # provide visual indicators of the reboot enclosure.mouth_text(mycroft.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 ws.emit(Message("system.reboot")) return ws.emit(Message('mycroft.internet.connected')) # check for pairing, if not automatically start pairing if not is_paired(): # begin the process payload = {'utterances': ["pair my device"], 'lang': "en-us"} ws.emit(Message("recognizer_loop:utterance", payload)) else: if is_paired(): # Skip the message when unpaired because the prompt to go # to home.mycrof.ai will be displayed by the pairing skill enclosure.mouth_text(mycroft.dialog.get("message_updating")) from mycroft.api import DeviceApi api = DeviceApi() api.update_version() else: thread = Timer(1, check_connection) thread.daemon = True thread.start()
def skill_gid(self): """ Finalizes the skill gid to include device uuid if needed. """ if is_paired(): return self.__skill_gid.replace('@|', '@{}|'.format( DeviceApi().identity.uuid)) else: return self.__skill_gid
def _poll_skill_settings(self): """ If identifier exists for this skill poll to backend to request settings and store it if it changes TODO: implement as websocket Args: hashed_meta (int): the hashed identifier """ original = hash(str(self)) try: if not is_paired(): pass elif not self._complete_intialization: self.initialize_remote_settings() if not self._complete_intialization: return # unable to do remote sync else: self.update_remote() except Exception as e: LOG.error('Failed to fetch skill settings:\n{}'.format(e)) finally: # Call callback for updated settings if self.changed_callback and hash(str(self)) != original: self.changed_callback() # this is used in core so do not delete! if self.is_alive: # continues to poll settings every 5 minutes t = Timer(300, self._poll_skill_settings) t.daemon = True t.start()
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 download(self, message=None): """Download the settings stored on the backend and check for changes When used as a messagebus handler a message is passed but not used. """ if not self.sync_enabled: return if is_paired(): remote_settings = self._get_remote_settings() if remote_settings: settings_changed = self.last_download_result != remote_settings if settings_changed: LOG.debug('Skill settings changed since last download') self._emit_settings_change_events(remote_settings) self.last_download_result = remote_settings save_remote_settings_cache(remote_settings) else: LOG.debug('No skill settings changes since last download') else: LOG.debug('Settings not downloaded - device is not paired') # If this method is called outside of the timer loop, ensure the # existing timer is canceled before starting a new one. if self.download_timer: self.download_timer.cancel() if self.continue_downloading: self.download_timer = Timer(ONE_MINUTE, self.download) self.download_timer.daemon = True self.download_timer.start()
def download(self): """Download the settings stored on the backend and check for changes""" if is_paired(): download_success = self._get_remote_settings() if download_success: self.settings_changed = (self.last_download_result != self.remote_settings) if self.settings_changed: LOG.debug('Skill settings changed since last download') self._emit_settings_change_events() self.last_download_result = self.remote_settings else: LOG.debug('No skill settings changes since last download') else: LOG.debug('Settings not downloaded - device is not paired') # If this method is called outside of the timer loop, ensure the # existing timer is canceled before starting a new one. if self.download_timer: self.download_timer.cancel() if self.continue_downloading: self.download_timer = Timer(ONE_MINUTE, self.download) self.download_timer.daemon = True self.download_timer.start()
def _poll_skill_settings(self): """ If identifier exists for this skill poll to backend to request settings and store it if it changes TODO: implement as websocket """ original = hash(str(self)) try: if not is_paired(): pass elif not self._complete_intialization: self.initialize_remote_settings() if not self._complete_intialization: return # unable to do remote sync else: self.update_remote() except Exception as e: LOG.exception('Failed to fetch skill settings: {}'.format(repr(e))) finally: # Call callback for updated settings if self.changed_callback and hash(str(self)) != original: self.changed_callback() if self._poll_timer: self._poll_timer.cancel() if not self._is_alive: return # continues to poll settings every minute self._poll_timer = Timer(60, self._poll_skill_settings) self._poll_timer.daemon = True self._poll_timer.start()
def _poll_skill_settings(self): """ If identifier exists for this skill poll to backend to request settings and store it if it changes TODO: implement as websocket """ original = hash(str(self)) try: if not is_paired(): pass elif not self._complete_intialization: self.initialize_remote_settings() else: self.update_remote() except Exception as e: LOG.exception('Failed to fetch skill settings: {}'.format(repr(e))) finally: # Call callback for updated settings if self._complete_intialization: if self.changed_callback and hash(str(self)) != original: self.changed_callback() if self._poll_timer: self._poll_timer.cancel() if not self._is_alive: return # continues to poll settings every minute self._poll_timer = Timer(1 * 60, self._poll_skill_settings) self._poll_timer.daemon = True self._poll_timer.start()
def upload(self): """Upload the contents of the settingsmeta file to Mycroft servers. The settingsmeta file does not change often, if at all. Only perform the upload if a change in the file is detected. """ synced = False if is_paired(): self.api = DeviceApi() if self.api.identity.uuid: settings_meta_file_exists = (self.json_path.is_file() or self.yaml_path.is_file()) if settings_meta_file_exists: self._load_settings_meta_file() self._update_settings_meta() LOG.debug('Uploading settings meta for ' + self.skill_gid) synced = self._issue_api_call() else: LOG.debug('settingsmeta.json not uploaded - no identity') else: LOG.debug('settingsmeta.json not uploaded - device is not paired') if not synced and not self._stopped: self.upload_timer = Timer(ONE_MINUTE, self.upload) self.upload_timer.daemon = True self.upload_timer.start()
def __init__(self, cache=None): super(RemoteConf, self).__init__(None) cache = cache or '/opt/mycroft/web_config_cache.json' from mycroft.api import is_paired if not is_paired(): self.load_local(cache) return try: # Here to avoid cyclic import from mycroft.api import DeviceApi api = DeviceApi() setting = api.get_settings() location = api.get_location() if location: setting["location"] = location # Remove server specific entries config = {} translate_remote(config, setting) for key in config: self.__setitem__(key, config[key]) self.store(cache) except HTTPError as e: LOG.error("RequestException fetching remote configuration: %s" % e.response.status_code) self.load_local(cache) except Exception as e: LOG.error("Failed to fetch remote configuration: %s" % repr(e), exc_info=True) self.load_local(cache)
def initialize_remote_settings(self): """ initializes the remote settings to the server """ # if the settingsmeta file exists (and is valid) # this block of code is a control flow for # different scenarios that may arises with settingsmeta self.load_skill_settings_from_file() # loads existing settings.json settings_meta = self._load_settings_meta() if not settings_meta: return if not is_paired(): return self._device_identity = self.api.identity.uuid self._api_path = "/" + self._device_identity + "/skill" try: self._user_identity = self.api.get()['user']['uuid'] except RequestException: return settings = self._request_my_settings(self.skill_gid) if settings: self.save_skill_settings(settings) # TODO if this skill_gid is not a modified version check if a modified # version exists on the server and delete it # Always try to upload settingsmeta on startup self._upload_meta(settings_meta, self.skill_gid) self._complete_intialization = True
def update_skills_json(self): skills_config = self.config_core['skills'] upload_allowed = skills_config.get('upload_skill_manifest', False) if upload_allowed and is_paired(): try: DeviceApi().upload_skills_data(self.msm.device_skill_state) except Exception: self.log.exception('Could not upload skill manifest')
def handle_paired(self, message): draw_file(self.find_resource('5-pairing-success.fb', 'ui')) time.sleep(5) draw_file(self.find_resource('6-intro.fb', 'ui')) time.sleep(15) draw_file(self.find_resource('mycroft.fb', 'ui')) if not is_paired(): self.bus.remove('enclosure.mouth.text', self.handle_show_text)
def _get_pairing_status(self): """Set an instance attribute indicating the device's pairing status""" try: self.is_paired = is_paired(ignore_errors=False) except BackendDown: LOG.error('Cannot complete device updates due to backend issues.') self.backend_down = True if self.is_paired: LOG.info('Device is paired')
def __init__(self, cache=None): super().__init__(None) cache = cache or WEB_CONFIG_CACHE from mycroft.api import is_paired if not is_paired(): self.load_local(cache) return try: # Here to avoid cyclic import from mycroft.api import DeviceApi from mycroft.api import is_backend_disabled if is_backend_disabled(): # disable options that require backend config = { "server": { "metrics": False, "sync_skill_settings": False }, "skills": {"upload_skill_manifest": False}, "opt_in": False } for key in config: self.__setitem__(key, config[key]) else: api = DeviceApi() setting = api.get_settings() location = None try: location = api.get_location() except RequestException as e: LOG.error("RequestException fetching remote location: {}" .format(str(e))) if exists(cache) and isfile(cache): location = load_commented_json(cache).get('location') if location: setting["location"] = location # Remove server specific entries config = {} translate_remote(config, setting) for key in config: self.__setitem__(key, config[key]) self.store(cache) except RequestException as e: LOG.error("RequestException fetching remote configuration: {}" .format(str(e))) self.load_local(cache) except Exception as e: LOG.error("Failed to fetch remote configuration: %s" % repr(e), exc_info=True) self.load_local(cache)
def post_manifest(self, reload_skills_manifest=False): """Post the manifest of the device's skills to the backend.""" upload_allowed = self.config['skills'].get('upload_skill_manifest') if upload_allowed and is_paired(): if reload_skills_manifest: self.msm.clear_cache() try: device_api = DeviceApi() device_api.upload_skills_data(self.msm.device_skill_state) except Exception: LOG.exception('Could not upload skill manifest')
def try_update_system(platform): bus.emit(Message('system.update')) msg = Message('system.update', { 'paired': is_paired(), 'platform': platform }) resp = bus.wait_for_response(msg, 'system.update.processing') if resp and (resp.data or {}).get('processing', True): bus.wait_for_response(Message('system.update.waiting'), 'system.update.complete', 1000)
def _init_blank_meta(self): """ Send blank settingsmeta to remote. """ try: if not is_paired() and self.is_alive: self._blank_poll_timer = Timer(60, self._init_blank_meta) self._blank_poll_timer.daemon = True self._blank_poll_timer.start() else: self.initialize_remote_settings() except Exception as e: LOG.exception('Failed to send blank meta: {}'.format(repr(e)))
def handle_internet_connected(self, message): """ System came online later after booting. """ if is_paired(): self.enclosure.mouth_reset() else: # If we are not paired the pairing process will begin. # Cannot handle from mycroft.not.paired event because # we trigger first pairing with an utterance. draw_file(self.find_resource('3-wifi-success.fb', 'ui')) time.sleep(5) draw_file(self.find_resource('4-pairing-home.fb', 'ui')) self.bus.on('enclosure.mouth.text', self.handle_show_text)
def check_connection(): if connected(): ws.emit(Message('mycroft.internet.connected')) # check for pairing, if not automatically start pairing if not is_paired(): # begin the process payload = {'utterances': ["pair my device"], 'lang': "en-us"} ws.emit(Message("recognizer_loop:utterance", payload)) else: thread = Timer(1, check_connection) thread.daemon = True thread.start()
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
def report_metric(name, data): """ Report a general metric to the Mycroft servers Args: name (str): Name of metric. Must use only letters and hyphens data (dict): JSON dictionary to report. Must be valid JSON """ try: if is_paired() and Configuration().get()['opt_in']: DeviceApi().report_metric(name, data) except requests.RequestException as e: LOG.error('Metric couldn\'t be uploaded, due to a network error ({})' .format(e))
def initialize_remote_settings(self): """ initializes the remote settings to the server """ # if settingsmeta.json exists (and is valid) # this block of code is a control flow for # different scenarios that may arises with settingsmeta self.load_skill_settings_from_file() # loads existing settings.json settings_meta = self._load_settings_meta() if not settings_meta: return if not is_paired(): return self._device_identity = self.api.identity.uuid self._api_path = "/" + self._device_identity + "/skill" try: self._user_identity = self.api.get()['user']['uuid'] except RequestException: return hashed_meta = self._get_meta_hash(settings_meta) skill_settings = self._request_other_settings(hashed_meta) # if hash is new then there is a diff version of settingsmeta if self._is_new_hash(hashed_meta): # first look at all other devices on user account to see # if the settings exist. if it does then sync with device if skill_settings: # not_owner flags that this settings is loaded from # another device. If a skill settings doesn't have # not_owner, then the skill is created from that device self['not_owner'] = True self.save_skill_settings(skill_settings) else: # upload skill settings if uuid = self._load_uuid() if uuid is not None: self._delete_metadata(uuid) self._upload_meta(settings_meta, hashed_meta) else: # hash is not new if skill_settings is not None: self['not_owner'] = True self.save_skill_settings(skill_settings) else: settings = self._request_my_settings(hashed_meta) if settings is None: # metadata got deleted from Home, send up self._upload_meta(settings_meta, hashed_meta) else: self.save_skill_settings(settings) self._complete_intialization = True
def check_connection(): if connected(): ws.emit(Message('mycroft.internet.connected')) # check for pairing, if not automatically start pairing if not is_paired(): # begin the process payload = { 'utterances': ["pair my device"], 'lang': "en-us" } ws.emit(Message("recognizer_loop:utterance", payload)) else: thread = Timer(1, check_connection) thread.daemon = True thread.start()
def check_connection(): """ Check for network connection. If not paired trigger pairing. Runs as a Timer every second until connection is detected. """ if connected(): ws.emit(Message('mycroft.internet.connected')) # check for pairing, if not automatically start pairing if not is_paired(): # begin the process payload = {'utterances': ["pair my device"], 'lang': "en-us"} ws.emit(Message("recognizer_loop:utterance", payload)) else: thread = Timer(1, check_connection) thread.daemon = True thread.start()
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 __init__(self, cache=None): super(RemoteConf, self).__init__(None) cache = cache or join(xdg.BaseDirectory.xdg_cache_home, 'mycroft', 'web_cache.json') from mycroft.api import is_paired if not is_paired(): self.load_local(cache) return try: # Here to avoid cyclic import from mycroft.api import DeviceApi api = DeviceApi() setting = api.get_settings() location = None try: location = api.get_location() except RequestException as e: LOG.error( "RequestException fetching remote location: {}".format( str(e))) if exists(cache) and isfile(cache): location = load_commented_json(cache).get('location') if location: setting["location"] = location # Remove server specific entries config = {} translate_remote(config, setting) for key in config: self.__setitem__(key, config[key]) self.store(cache) except RequestException as e: LOG.error( "RequestException fetching remote configuration: {}".format( str(e))) self.load_local(cache) except Exception as e: LOG.error("Failed to fetch remote configuration: %s" % repr(e), exc_info=True) self.load_local(cache)
def write_skills_data(skills_data): """ Backwards compatibility write function. Converts the old style skill.json storage to new style storage. TODO: Remove in 19.02 """ msm = SkillManager.create_msm() with msm.lock, SkillManager.get_lock(): msm_data = msm.load_skills_data() skills = [] for key in skills_data: skills_data[key]['name'] = key skills.append(skills_data[key]) msm_data['skills'] = skills msm.skills_data_hash = '' # Force write msm.write_skills_data(msm_data) if is_paired(): DeviceApi().upload_skills_data(msm_data)
def __init__(self, cache=None): super(RemoteConf, self).__init__(None) cache = cache or WEB_CONFIG_CACHE from mycroft.api import is_paired if not is_paired(): self.load_local(cache) return try: # Here to avoid cyclic import from mycroft.api import DeviceApi api = DeviceApi() setting = api.get_settings() try: location = api.get_location() except RequestException as e: LOG.error("RequestException fetching remote location: {}" .format(str(e))) if exists(cache) and isfile(cache): location = load_commented_json(cache).get('location') if location: setting["location"] = location # Remove server specific entries config = {} translate_remote(config, setting) for key in config: self.__setitem__(key, config[key]) self.store(cache) except RequestException as e: LOG.error("RequestException fetching remote configuration: {}" .format(str(e))) self.load_local(cache) except Exception as e: LOG.error("Failed to fetch remote configuration: %s" % repr(e), exc_info=True) self.load_local(cache)
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 check_connection(): """ Check for network connection. If not paired trigger pairing. Runs as a Timer every second until connection is detected. """ if connected(): ws.emit(Message('mycroft.internet.connected')) # check for pairing, if not automatically start pairing if not is_paired(): # begin the process payload = { 'utterances': ["pair my device"], 'lang': "en-us" } ws.emit(Message("recognizer_loop:utterance", payload)) else: from mycroft.api import DeviceApi api = DeviceApi() api.update_version() 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() 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 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()
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 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()
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(ws) 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(mycroft.dialog.get("message_synching.clock")) # Force a sync of the local clock with the internet ws.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((monotonic.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'. # ws.emit(Message("speak", {'utterance': mycroft.dialog.get("time.changed.reboot")})) wait_while_speaking() # provide visual indicators of the reboot enclosure.mouth_text(mycroft.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 ws.emit(Message("system.reboot")) return ws.emit(Message('mycroft.internet.connected')) # check for pairing, if not automatically start pairing if not is_paired(): # begin the process payload = { 'utterances': ["pair my device"], 'lang': "en-us" } ws.emit(Message("recognizer_loop:utterance", payload)) else: if is_paired(): # Skip the message when unpaired because the prompt to go # to home.mycrof.ai will be displayed by the pairing skill enclosure.mouth_text(mycroft.dialog.get("message_updating")) from mycroft.api import DeviceApi api = DeviceApi() api.update_version() else: thread = Timer(1, check_connection) thread.daemon = True thread.start()