def handle_converse_request(self, message): """ Check if the targeted skill id can handle conversation If supported, the conversation is invoked. """ skill_id = int(message.data["skill_id"]) utterances = message.data["utterances"] lang = message.data["lang"] # loop trough skills list and call converse for skill with skill_id for skill in self.loaded_skills: if self.loaded_skills[skill]["id"] == skill_id: try: instance = self.loaded_skills[skill]["instance"] except BaseException: LOG.error("converse requested but skill not loaded") self.ws.emit(Message("skill.converse.response", { "skill_id": 0, "result": False})) return try: result = instance.converse(utterances, lang) self.ws.emit(Message("skill.converse.response", { "skill_id": skill_id, "result": result})) return except BaseException: LOG.error( "Converse method malformed for skill " + str(skill_id)) self.ws.emit(Message("skill.converse.response", {"skill_id": 0, "result": False}))
def send(self, *args, **kwargs): """ Send to all registered GUIs. """ for gui in self.GUIs.values(): if gui.socket: gui.socket.send(*args, **kwargs) else: LOG.error('GUI connection {} has no socket!'.format(gui))
def play_audio_file(uri: str): """ Play an audio file. This wraps the other play_* functions, choosing the correct one based on the file extension. The function will return directly and play the file in the background. Arguments: uri: uri to play Returns: subprocess.Popen object. None if the format is not supported or an error occurs playing the file. """ extension_to_function = { '.wav': play_wav, '.mp3': play_mp3, '.ogg': play_ogg } _, extension = splitext(uri) play_function = extension_to_function.get(extension.lower()) if play_function: return play_function(uri) else: LOG.error("Could not find a function capable of playing {uri}." " Supported formats are {keys}." .format(uri=uri, keys=list(extension_to_function.keys()))) return None
def __init__(self, emitter): FallbackSkill.__init__(self) self.config = ConfigurationManager.get()['padatious'] intent_cache = expanduser(self.config['intent_cache']) try: from padatious import IntentContainer except ImportError: LOG.error('Padatious not installed. Please re-run dev_setup.sh') try: call(['notify-send', 'Padatious not installed', 'Please run build_host_setup and dev_setup again']) except OSError: pass return ver = get_distribution('padatious').version if ver != PADATIOUS_VERSION: LOG.warning('Using Padatious v' + ver + '. Please re-run ' + 'dev_setup.sh to install ' + PADATIOUS_VERSION) self.container = IntentContainer(intent_cache) self.emitter = emitter self.emitter.on('padatious:register_intent', self.register_intent) self.emitter.on('padatious:register_entity', self.register_entity) self.register_fallback(self.handle_fallback, 5) self.finished_training_event = Event() self.train_delay = self.config['train_delay'] self.train_time = get_time() + self.train_delay self.wait_and_train()
def shutdown(self): for s in self.service: try: LOG.info('shutting down ' + s.name) s.shutdown() except Exception as e: LOG.error('shutdown of ' + s.name + ' failed: ' + repr(e)) # remove listeners self.bus.remove('mycroft.audio.service.play', self._play) self.bus.remove('mycroft.audio.service.queue', self._queue) self.bus.remove('mycroft.audio.service.pause', self._pause) self.bus.remove('mycroft.audio.service.resume', self._resume) self.bus.remove('mycroft.audio.service.stop', self._stop) self.bus.remove('mycroft.audio.service.next', self._next) self.bus.remove('mycroft.audio.service.prev', self._prev) self.bus.remove('mycroft.audio.service.track_info', self._track_info) self.bus.remove('mycroft.audio.service.seek_forward', self._seek_forward) self.bus.remove('mycroft.audio.service.seek_backward', self._seek_backward) self.bus.remove('recognizer_loop:audio_output_start', self._lower_volume) self.bus.remove('recognizer_loop:record_begin', self._lower_volume) self.bus.remove('recognizer_loop:audio_output_end', self._restore_volume) self.bus.remove('recognizer_loop:record_end', self._restore_volume) self.bus.remove('mycroft.stop', self._stop)
def _read_data(): """ Reads the file in (/tmp/mycroft/ipc/managers/disp_info) and returns the the data as python dict """ managerIPCDir = os.path.join(get_ipc_directory(), "managers") path = os.path.join(managerIPCDir, "disp_info") permission = "r" if os.path.isfile(path) else "w+" if permission == "w+" and os.path.isdir(managerIPCDir) is False: os.makedirs(managerIPCDir) data = {} try: with open(path, permission) as dispFile: if os.stat(str(dispFile.name)).st_size != 0: data = json.load(dispFile) except Exception as e: LOG.error(e) os.remove(path) _read_data() return data
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 """ try: if not self._complete_intialization: self.initialize_remote_settings() if not self._complete_intialization: return # unable to do remote sync else: original = hash(str(self)) self.update_remote() # Call callback for updated settings if self.changed_callback and hash(str(self)) != original: self.changed_callback() except Exception as e: LOG.error(e) LOG.exception("") # this is used in core so do not delete! if self.is_alive: # continues to poll settings every 60 seconds t = Timer(60, self._poll_skill_settings) t.daemon = True t.start()
def __init__(self): super(GoVivaceSTT, self).__init__() self.default_uri = "https://services.govivace.com:49149/telephony" if not self.lang.startswith("en") and not self.lang.startswith("es"): LOG.error("GoVivace STT only supports english and spanish") raise NotImplementedError
def _read_data(): """ Writes the dictionary of state data from the IPC directory. Returns: dict: loaded state information """ managerIPCDir = os.path.join(get_ipc_directory(), "managers") path = os.path.join(managerIPCDir, "disp_info") permission = "r" if os.path.isfile(path) else "w+" if permission == "w+" and os.path.isdir(managerIPCDir) is False: os.makedirs(managerIPCDir) data = {} try: with open(path, permission) as dispFile: if os.stat(str(dispFile.name)).st_size != 0: data = json.load(dispFile) except Exception as e: LOG.error(e) os.remove(path) _read_data() return data
def __init__(self, cache=None): super(RemoteConf, self).__init__(None) cache = cache or '/opt/mycroft/web_config_cache.json' 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("HTTPError 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 mute_and_speak(utterance, ident): """ Mute mic and start speaking the utterance using selected tts backend. Args: utterance: The sentence to be spoken ident: Ident tying the utterance to the source query """ global tts_hash # update TTS object if configuration has changed if tts_hash != hash(str(config.get('tts', ''))): global tts # Stop tts playback thread tts.playback.stop() tts.playback.join() # Create new tts instance tts = TTSFactory.create() tts.init(bus) tts_hash = hash(str(config.get('tts', ''))) LOG.info("Speak: " + utterance) try: tts.execute(utterance, ident) except RemoteTTSTimeoutException as e: LOG.error(e) mimic_fallback_tts(utterance, ident) except Exception as e: LOG.error('TTS execution failed ({})'.format(repr(e)))
def read(self): while self.alive: try: data = self.serial.readline()[:-2] if data: self.process(data) except Exception as e: LOG.error("Reading error: {0}".format(e))
def deactivate_skill(self, message): """ Deactivate a skill. """ try: skill = message.data['skill'] if skill in [basename(s) for s in self.loaded_skills]: self.__deactivate_skill(skill) except Exception as e: LOG.error('Couldn\'t deactivate skill, {}'.format(repr(e)))
def flush(self): while self.alive: try: cmd = self.commands.get() self.serial.write(cmd + '\n') self.commands.task_done() except Exception as e: LOG.error("Writing error: {0}".format(e))
def __play(self, req): resp = req.result() if resp.status_code == 200: self.__save(resp.content) play_wav(self.filename).communicate() else: LOG.error( '%s Http Error: %s for url: %s' % (resp.status_code, resp.reason, resp.url))
def run(self): try: self.ws.on('message', LOG.debug) self.ws.on('open', self.load_skill) self.ws.on('error', LOG.error) self.ws.run_forever() except Exception as e: LOG.error("Error: {0}".format(e)) self.stop()
def on_gui_send_event(self, message): """ Send an event to the GUIs. """ try: data = {'type': 'mycroft.events.triggered', 'namespace': message.data.get('__from'), 'event_name': message.data.get('event_name'), 'params': message.data.get('params')} self.send(data) except Exception as e: LOG.error('Could not send event ({})'.format(repr(e)))
def get(): if (exists(VersionManager.__location) and isfile(VersionManager.__location)): try: with open(VersionManager.__location) as f: return json.load(f) except: LOG.error("Failed to load version from '%s'" % VersionManager.__location) return {"coreVersion": None, "enclosureVersion": None}
def get(): data_dir = expanduser(Configuration.get()['data_dir']) version_file = join(data_dir, 'version.json') if exists(version_file) and isfile(version_file): try: with open(version_file) as f: return json.load(f) except Exception: LOG.error("Failed to load version from '%s'" % version_file) return {"coreVersion": None, "enclosureVersion": None}
def handle_speak(event): """ Handle "speak" message """ config = Configuration.get() Configuration.init(bus) global _last_stop_signal # Get conversation ID if event.context and 'ident' in event.context: ident = event.context['ident'] else: ident = 'unknown' start = time.time() # Time of speech request with lock: stopwatch = Stopwatch() stopwatch.start() utterance = event.data['utterance'] if event.data.get('expect_response', False): # When expect_response is requested, the listener will be restarted # at the end of the next bit of spoken audio. bus.once('recognizer_loop:audio_output_end', _start_listener) # This is a bit of a hack for Picroft. The analog audio on a Pi blocks # for 30 seconds fairly often, so we don't want to break on periods # (decreasing the chance of encountering the block). But we will # keep the split for non-Picroft installs since it give user feedback # faster on longer phrases. # # TODO: Remove or make an option? This is really a hack, anyway, # so we likely will want to get rid of this when not running on Mimic if (config.get('enclosure', {}).get('platform') != "picroft" and len(re.findall('<[^>]*>', utterance)) == 0): chunks = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\;|\?)\s', utterance) for chunk in chunks: # Check if somthing has aborted the speech if (_last_stop_signal > start or check_for_signal('buttonPress')): # Clear any newly queued speech tts.playback.clear() break try: mute_and_speak(chunk, ident) except KeyboardInterrupt: raise except Exception: LOG.error('Error in mute_and_speak', exc_info=True) else: mute_and_speak(utterance, ident) stopwatch.stop() report_timing(ident, 'speech', stopwatch, {'utterance': utterance, 'tts': tts.__class__.__name__})
def _play(self, message): """ Implementation specific async method to handle playback. This allows mpg123 service to use the "next method as well as basic play/stop. """ LOG.info('SimpleAudioService._play') repeat = message.data.get('repeat', False) self._is_playing = True if isinstance(self.tracks[self.index], list): track = self.tracks[self.index][0] mime = self.tracks[self.index][1] mime = mime.split('/') else: # Assume string track = self.tracks[self.index] mime = find_mime(track) # Indicate to audio service which track is being played if self._track_start_callback: self._track_start_callback(track) # Replace file:// uri's with normal paths track = track.replace('file://', '') try: if 'mpeg' in mime[1]: self.process = play_mp3(track) elif 'ogg' in mime[1]: self.process = play_ogg(track) elif 'wav' in mime[1]: self.process = play_wav(track) else: # If no mime info could be determined guess mp3 self.process = play_mp3(track) except FileNotFoundError as e: LOG.error('Couldn\'t play audio, {}'.format(repr(e))) self.process = None # Wait for completion or stop request while (self.process and self.process.poll() is None and not self._stop_signal): sleep(0.25) if self._stop_signal: self.process.terminate() self.process = None self._is_playing = False return self.index += 1 # if there are more tracks available play next if self.index < len(self.tracks) or repeat: if self.index >= len(self.tracks): self.index = 0 self.bus.emit(Message('SimpleAudioServicePlay', {'repeat': repeat})) else: self._is_playing = False
def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: LOG.error(repr(e)) LOG.warning("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever()
def __deactivate_skill(self, skill): """ Deactivate a skill. """ for s in self.loaded_skills: if skill in s: skill = s break try: self.loaded_skills[skill]['active'] = False self.loaded_skills[skill]['instance'].default_shutdown() except Exception as e: LOG.error('Couldn\'t deactivate skill, {}'.format(repr(e)))
def __handle_stop(self, event): """ Handler for the "mycroft.stop" signal. Runs the user defined `stop()` method. """ self.stop_time = time.time() try: self.stop() except: LOG.error("Failed to stop skill: {}".format(self.name), exc_info=True)
def create_config(self, dict_name, config): model_file = join(RECOGNIZER_DIR, 'model', self.lang, 'hmm') if not exists(model_file): LOG.error('PocketSphinx model not found at ' + str(model_file)) config.set_string('-hmm', model_file) config.set_string('-dict', dict_name) config.set_string('-keyphrase', self.key_phrase) config.set_float('-kws_threshold', float(self.threshold)) config.set_float('-samprate', self.sample_rate) config.set_int('-nfft', 2048) config.set_string('-logfn', '/dev/null') return config
def load_skill_settings_from_file(self): """ If settings.json exist, open and read stored values into self """ if isfile(self._settings_path): with open(self._settings_path) as f: try: json_data = json.load(f) for key in json_data: self[key] = json_data[key] except Exception as e: # TODO: Show error on webUI. Dev will have to fix # metadata to be able to edit later. LOG.error(e)
def __init_serial(self): try: self.port = self.config.get("port") self.rate = self.config.get("rate") self.timeout = self.config.get("timeout") self.serial = serial.serial_for_url( url=self.port, baudrate=self.rate, timeout=self.timeout) LOG.info("Connected to: %s rate: %s timeout: %s" % (self.port, self.rate, self.timeout)) except: LOG.error("Impossible to connect to serial port: "+str(self.port)) raise
def write_skills_data(data: dict): skills_data_file = expanduser('~/.mycroft/skills.json') with open(skills_data_file, 'w') as f: json.dump(data, f) if (is_paired and Configuration.get()['skills'].get('upload_skill_manifest')): upload_data = SkillManager.convert_skills_data(data) try: DeviceApi().upload_skills_data(upload_data) except Exception as e: LOG.error('An error occured ({})'.format(e))
def enable_intent(self, intent_name): """Reenable a registered intent""" for (name, intent) in self.registered_intents: if name == intent_name: self.registered_intents.remove((name, intent)) intent.name = name self.register_intent(intent, None) LOG.debug('Enabling intent ' + intent_name) break else: LOG.error('Could not enable ' + intent_name + ', it hasn\'t been registered.')
def load_skill(skill_descriptor, emitter, skill_id, BLACKLISTED_SKILLS=None): """ load skill from skill descriptor. Args: skill_descriptor: descriptor of skill to load emitter: messagebus emitter skill_id: id number for skill Returns: MycroftSkill: the loaded skill or None on failure """ BLACKLISTED_SKILLS = BLACKLISTED_SKILLS or [] try: LOG.info("ATTEMPTING TO LOAD SKILL: " + skill_descriptor["name"] + " with ID " + str(skill_id)) if skill_descriptor['name'] in BLACKLISTED_SKILLS: LOG.info("SKILL IS BLACKLISTED " + skill_descriptor["name"]) return None skill_module = imp.load_module( skill_descriptor["name"] + MainModule, *skill_descriptor["info"]) if (hasattr(skill_module, 'create_skill') and callable(skill_module.create_skill)): # v2 skills framework skill = skill_module.create_skill() skill.settings.allow_overwrite = True skill.settings.load_skill_settings_from_file() skill.bind(emitter) skill.skill_id = skill_id skill.load_data_files(dirname(skill_descriptor['info'][1])) # Set up intent handlers skill.initialize() skill._register_decorated() LOG.info("Loaded " + skill_descriptor["name"]) # The very first time a skill is run, speak the intro first_run = skill.settings.get("__mycroft_skill_firstrun", True) if first_run: LOG.info("First run of "+skill_descriptor["name"]) skill.settings["__mycroft_skill_firstrun"] = False skill.settings.store() intro = skill.get_intro_message() if intro: skill.speak(intro) return skill else: LOG.warning( "Module %s does not appear to be skill" % ( skill_descriptor["name"])) except: LOG.error( "Failed to load skill: " + skill_descriptor["name"], exc_info=True) return None
def handle_current_weather(self, message): try: # Get a date from requests like "weather for next Tuesday" today = extract_datetime(" ")[0] when = extract_datetime(message.data.get('utterance'))[0] if today != when: LOG.info("Doing a forecast" + str(today) + " " + str(when)) return self.handle_forecast(message) report = self.__initialize_report(message) # Get current conditions currentWeather = self.owm.weather_at_place( report['full_location'], report['lat'], report['lon']).get_weather() report['condition'] = currentWeather.get_detailed_status() report['temp'] = self.__get_temperature(currentWeather, 'temp') report['icon'] = currentWeather.get_weather_icon_name() # Get forecast for the day # can get 'min', 'max', 'eve', 'morn', 'night', 'day' # Set time to 12 instead of 00 to accomodate for timezones forecastWeather = self.__get_forecast( today.replace( hour=12), report['full_location'], report['lat'], report['lon']) report['temp_min'] = self.__get_temperature(forecastWeather, 'min') report['temp_max'] = self.__get_temperature(forecastWeather, 'max') self.__report_weather("current", report) except HTTPError as e: self.__api_error(e) except Exception as e: LOG.error("Error: {0}".format(e))
def shutdown(self): for s in self.service: try: LOG.info('shutting down ' + s.name) s.shutdown() except Exception as e: LOG.error('shutdown of ' + s.name + ' failed: ' + repr(e)) # remove listeners self.ws.remove('mycroft.audio.service.play', self._play) self.ws.remove('mycroft.audio.service.queue', self._queue) self.ws.remove('mycroft.audio.service.pause', self._pause) self.ws.remove('mycroft.audio.service.resume', self._resume) self.ws.remove('mycroft.audio.service.stop', self._stop) self.ws.remove('mycroft.audio.service.next', self._next) self.ws.remove('mycroft.audio.service.prev', self._prev) self.ws.remove('mycroft.audio.service.track_info', self._track_info) self.ws.remove('recognizer_loop:audio_output_start', self._lower_volume) self.ws.remove('recognizer_loop:record_begin', self._lower_volume) self.ws.remove('recognizer_loop:audio_output_end', self._restore_volume) self.ws.remove('recognizer_loop:record_end', self._restore_volume) self.ws.remove('mycroft.stop', self._stop)
def run_requirements_sh(self, skill_folder): skill = self.skills[skill_folder] reqs = join(skill["path"], "requirements.sh") # TODO check hash before re running if exists(reqs): LOG.info("running requirements.sh for: " + skill_folder) # make exec subprocess.call((["chmod", "+x", reqs])) # handle sudo if self.platform in ["desktop", "kde", "jarbas"]: # gksudo args = ["gksu", "bash", reqs] else: # no sudo args = ["bash", reqs] rc = subprocess.call(args) LOG.debug("Requirements.sh return code:" + str(rc)) if rc != 0: LOG.error("Requirements.sh failed with error code: " + str(rc)) raise SystemRequirementsException LOG.info("Successfully ran requirements.sh for " + skill_folder) else: LOG.info("no requirements.sh to run") return False return True
def read(self): try: message = self.queue.get(timeout=0.5) except Empty: return if message is None: return tag, data = message if tag == AUDIO_DATA: if self.state.sleeping: self.wake_up(data) else: self.process(data) elif tag == STREAM_START: self.stt.stream_start() elif tag == STREAM_DATA: self.stt.stream_data(data) elif tag == STREAM_STOP: self.stt.stream_stop() else: LOG.error("Unknown audio queue type %r" % message)
def run(self): """ Load skills and update periodically from disk and internet """ self.remove_git_locks() self._connected_event.wait() has_loaded = False # check if skill updates are enabled update = Configuration.get()["skills"]["auto_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(): # Update skills once an hour if update is enabled if time.time() >= self.next_download and update: self.download_skills() # Look for recently changed skill(s) needing a reload # checking skills dir and getting all skills there skill_paths = glob(join(self.msm.skills_dir, '*/')) still_loading = False for skill_path in skill_paths: try: still_loading = (self._load_or_reload_skill(skill_path) or still_loading) except Exception as e: LOG.error('(Re)loading of {} failed ({})'.format( skill_path, repr(e))) if not has_loaded and not still_loading and len(skill_paths) > 0: has_loaded = True LOG.info("Skills all loaded!") self.bus.emit(Message('mycroft.skills.initialized')) self._unload_removed(skill_paths) # Pause briefly before beginning next scan time.sleep(2)
def get_services(services_folder): """ Load and initialize services from all subfolders. Args: services_folder: base folder to look for services in. Returns: Sorted list of audio services. """ LOG.info("Loading services from " + services_folder) services = [] possible_services = listdir(services_folder) for i in possible_services: location = join(services_folder, i) if (isdir(location) and not MAINMODULE + ".py" in listdir(location)): for j in listdir(location): name = join(location, j) if (not isdir(name) or not MAINMODULE + ".py" in listdir(name)): continue try: services.append(create_service_descriptor(name)) except Exception: LOG.error('Failed to create service from ' + name, exc_info=True) if (not isdir(location) or not MAINMODULE + ".py" in listdir(location)): continue try: services.append(create_service_descriptor(location)) except Exception: LOG.error('Failed to create service from ' + location, exc_info=True) return sorted(services, key=lambda p: p.get('name'))
def play(self, device, uris=None, context_uri=None): """ Start playback of tracks, albums or artist. Can play either a list of uris or a context_uri for things like artists and albums. Both uris and context_uri shouldn't be provided at the same time. Args: device (int): device id to start playback on uris (list): list of track uris to play context_uri (str): Spotify context uri for playing albums or artists. """ data = {} if uris: data['uris'] = uris elif context_uri: data['context_uri'] = context_uri path = 'me/player/play?device_id={}'.format(device) try: self._put(path, payload=data) except Exception as e: LOG.error(e) raise
def handle_next_hour(self, message): try: report = self.__initialize_report(message) # Get near-future forecast forecastWeather = self.owm.three_hours_forecast( report['full_location'], report['lat'], report['lon']).get_forecast().get_weathers()[0] # NOTE: The 3-hour forecast uses different temperature labels, # temp, temp_min and temp_max. report['temp'] = self.__get_temperature(forecastWeather, 'temp') report['temp_min'] = self.__get_temperature( forecastWeather, 'temp_min') report['temp_max'] = self.__get_temperature( forecastWeather, 'temp_max') report['condition'] = forecastWeather.get_detailed_status() report['icon'] = forecastWeather.get_weather_icon_name() self.__report_weather("hour", report) except HTTPError as e: self.__api_error(e) except Exception as e: LOG.error("Error: {0}".format(e))
def on_error(self, error): """ On error start trying to reconnect to the websocket. """ if isinstance(error, WebSocketConnectionClosedException): LOG.warning('Could not send message because connection has closed') else: LOG.exception('=== ' + repr(error) + ' ===') try: self.emitter.emit('error', error) if self.client.keep_running: self.client.close() except Exception as e: LOG.error('Exception closing websocket: ' + repr(e)) LOG.warning("Message Bus Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) try: self.emitter.emit('reconnecting') self.client = self.create_client() self.run_forever() except WebSocketException: pass
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() 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 play_film_by_search(self, kodi_id, film_search): # Todo need to remove kodi_id (kodipydent) reference results = self.find_films_matching(kodi_id, film_search) self.movie_list = results self.movie_index = 0 if len(results) == 1: self.play_film(results[0]['movieid']) elif len(results): self.set_context('NavigateContextKeyword', 'NavigateContext') if self.notifier_bool: try: self.post_kodi_notification(film_search + ' : '+ str(len(results))) except Exception as e: LOG.error(e) self.on_websettings_changed() self.speak_dialog('multiple.results', data={"result": str(len(results))}, expect_response=True) else: if self.notifier_bool: try: self.post_kodi_notification(film_search + ' : '+ str(len(results))) except Exception as e: LOG.error(e) self.on_websettings_changed() self.speak_dialog('no.results', data={"result": film_search}, expect_response=False)
def play_film_by_search( self, kodi_id, film_search): # called from, handle_play_film_intent results = self.find_films_matching(kodi_id, film_search) self.movie_list = results self.movie_index = 0 if len(results) == 1: self.play_film(kodi_id, results[0]['movieid']) elif len(results): msg_payload = "I found, " + str( len(results)) + ", results, would you like me to list them?" if self.notifier_bool: try: self.kodi_instance.GUI.ShowNotification( title="Mycroft.AI", message=msg_payload, displaytime=2500) except Exception as e: LOG.error(e) self.on_websettings_changed() self.speak_dialog('context', data={"result": msg_payload}, expect_response=True) else: msg_payload = "I found no results for the search: {}.".format( film_search) if self.notifier_bool: try: self.kodi_instance.GUI.ShowNotification( title="Mycroft.AI", message=msg_payload, displaytime=2500) except Exception as e: LOG.error(e) self.on_websettings_changed() self.stop_navigation(msg_payload)
def play_wav(uri): """ Play a wav-file. This will use the application specified in the mycroft config and play the uri passed as argument. The function will return directly and play the file in the background. Arguments: uri: uri to play Returns: subprocess.Popen object """ config = mycroft.configuration.Configuration.get() play_cmd = config.get("play_wav_cmdline") play_wav_cmd = str(play_cmd).split(" ") for index, cmd in enumerate(play_wav_cmd): if cmd == "%1": play_wav_cmd[index] = (get_http(uri)) try: return subprocess.Popen(play_wav_cmd) except Exception as e: LOG.error("Failed to launch WAV: {}".format(play_wav_cmd)) LOG.debug("Error: {}".format(repr(e)), exc_info=True) return None
def pause_all(kodi_path, player_id=1): """ Must perform a GetActivePlayer to retrieve player_id """ api_path = kodi_path + "/jsonrpc" json_header = {'content-type': 'application/json'} method = "Player.PlayPause" kodi_payload = { "jsonrpc": "2.0", "method": method, "params": { "playerid": int(player_id), "play": False }, "id": 1 } try: kodi_response = requests.post(api_path, data=json.dumps(kodi_payload), headers=json_header) LOG.info(kodi_response.text) return kodi_response except Exception as e: LOG.error(e)
def _send_settings_meta(self, settings_meta, hashed_meta): """ Send settingsmeta.json to the backend. Args: settings_meta (dict): dictionary of the current settings meta data hased_meta (int): hash value for settings meta data Returns: str: uuid, a unique id for the setting meta data """ try: settings_meta["identifier"] = str(hashed_meta) self._put_metadata(settings_meta) settings = self._get_remote_settings() skill_identity = str(hashed_meta) uuid = None # TODO: note uuid should be returned from the put request for skill_setting in settings: if skill_setting['identifier'] == skill_identity: uuid = skill_setting["uuid"] return uuid except Exception as e: LOG.error(e)
def handle_movie_search(self, message): # Pull data from intent search_title = message.data.get('title') search_actor = message.data.get('actor') # Talk to the user self.speak_dialog('search.for.that') # If the user specified an actor, search for the first matching title on IMDB to get the IMDB ID if search_actor: try: self.results = self.search_imdb_actor(search_title, search_actor) # Didn't find anything in IMDB if not self.results: self.speak_dialog('what.movie') # Found one in IMDB, go find it via utelly, etc else: self.movie_search(0) # Something went wrong with IMDB search except Exception as e: self.speak_dialog('unable.to.connect', data={'service':'imdb'}) LOG.error("Error searching imdb: " + str(e)) # If no actor was specified, find the top three and let the user choose the correct option else: # Make IMDB api request and get results try: # Search IMDB for IMDB ID (top three results) self.results = self.search_imdb(search_title)[0:3] self.speak_dialog('movies.results') # List top three videos found on IMDB for the user to chose from self.list_movies() # Something went wrong talking to IMDB except Exception as e: self.speak_dialog('unable.to.connect', data={'service':'imdb'}) LOG.error("Error searching imdb: " + str(e))
def _load_settings_meta(self): """ Load settings metadata from the skill folder. If no settingsmeta exists a basic settingsmeta will be created containing a basic identifier. Returns: (dict) settings meta """ if self._meta_path and os.path.isfile(self._meta_path): _, ext = os.path.splitext(self._meta_path) json_file = True if ext.lower() == ".json" else False try: with open(self._meta_path, encoding='utf-8') as f: if json_file: data = json.load(f) else: data = yaml.safe_load(f) except Exception as e: LOG.error("Failed to load setting file: " + self._meta_path) LOG.error(repr(e)) data = {} else: data = {} # Insert skill_gid and display_name data['skill_gid'] = self.skill_gid data['display_name'] = (self.display_name or data.get('name') or display_name(self.name)) # Backwards compatibility: if 'name' not in data: data['name'] = data['display_name'] return data
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)
def handle_latest_intent(self, message): try: self.stop() feeddata = feedparser.parse(self.url_rss) data = feeddata.entries[0] # Stop anything already playing url = data.enclosures[0]['url'] LOG.info('latest') LOG.info(url) # After the intro, start the no agenda stream # if audio service module is available use it wait_while_speaking() if self.audioservice: LOG.info('AudioService') self.audioservice.play(url, message.data['utterance']) else: # othervice use normal mp3 playback LOG.info('playmp3') self.process = play_mp3(url) except Exception as e: LOG.error("Error: {0}".format(e))
def handle_forecast(self, message): try: report = self.__initialize_report(message) # Get a date from spoken request when = extract_datetime(message.data.get('utterance'))[0] # Get forecast for the day forecastWeather = self.__get_forecast( when, report['full_location'], report['lat'], report['lon']) if forecastWeather is None: self.speak_dialog("no forecast", {'day': self.__to_day(when)}) return # Can get temps for 'min', 'max', 'eve', 'morn', 'night', 'day' report['temp'] = self.__get_temperature(forecastWeather, 'day') report['temp_min'] = self.__get_temperature(forecastWeather, 'min') report['temp_max'] = self.__get_temperature(forecastWeather, 'max') report['icon'] = forecastWeather.get_weather_icon_name() # TODO: Run off of status IDs instead of the status text? # This converts a status like "sky is clear" to a different # text and tense, because you don't want: # "Friday it will be 82 and the sky is clear", it should be # 'Friday it will be 82 and the sky will be clear' or just # 'Friday it will be 82 and clear. report['condition'] = self.__translate( forecastWeather.get_detailed_status(), True) report['day'] = self.__to_day(when) # Tuesday, tomorrow, etc. self.__report_weather("forecast", report) except HTTPError as e: self.__api_error(e) except Exception as e: LOG.error("Error: {0}".format(e))
def _send_settings_meta(self, settings_meta): """ Send settingsmeta to the server. Args: settings_meta (dict): dictionary of the current settings meta Returns: dict: uuid, a unique id for the setting meta data """ if self._meta_upload: try: uuid = self.api.upload_skill_metadata( self._type_cast(settings_meta, to_platform='web')) return uuid except HTTPError as e: if e.response.status_code in [422, 500, 501]: self._meta_upload = False raise DelayRequest else: LOG.error(e) return None except Exception as e: LOG.error(e) return None
def skip_play(kodi_path, dir_skip): """ Confirm that Koid i splaying before Executing this script """ api_path = kodi_path + "/jsonrpc" json_header = {'content-type': 'application/json'} method = "Player.Seek" kodi_payload = { "jsonrpc": "2.0", "method": method, "params": { "playerid": 1, "value": dir_skip }, "id": 1 } try: kodi_response = requests.post(api_path, data=json.dumps(kodi_payload), headers=json_header) LOG.info(kodi_response.text) return kodi_response except Exception as e: LOG.error(e)
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 wrapper(message): try: # Indicate that the skill handler is starting name = get_handler_name(handler) self.emitter.emit( Message("mycroft.skill.handler.start", data={'handler': name})) if need_self: # When registring from decorator self is required if len(getargspec(handler).args) == 2: handler(self, message) elif len(getargspec(handler).args) == 1: handler(self) elif len(getargspec(handler).args) == 0: # Zero may indicate multiple decorators, trying the # usual call signatures try: handler(self, message) except TypeError: handler(self) else: LOG.error("Unexpected argument count:" + str(len(getargspec(handler).args))) raise TypeError else: if len(getargspec(handler).args) == 2: handler(message) elif len(getargspec(handler).args) == 1: handler() else: LOG.error("Unexpected argument count:" + str(len(getargspec(handler).args))) raise TypeError self.settings.store() # Store settings if they've changed except Exception as e: # Convert "MyFancySkill" to "My Fancy Skill" for speaking name = re.sub("([a-z])([A-Z])", "\g<1> \g<2>", self.name) # TODO: Localize self.speak("An error occurred while processing a request in " + name) LOG.error("An error occurred while processing a request in " + self.name, exc_info=True) # indicate completion with exception self.emitter.emit( Message('mycroft.skill.handler.complete', data={ 'handler': name, 'exception': e.message })) # Indicate that the skill handler has completed self.emitter.emit( Message('mycroft.skill.handler.complete', data={'handler': name}))
def transcribe(self, audio): text = None try: # Invoke the STT engine on the audio clip text = self.stt.execute(audio).lower().strip() LOG.debug("STT: " + 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: text = "pair my device" # phrase to start the pairing process LOG.warning("Access Denied at mycroft.ai") except Exception as e: self.emitter.emit('recognizer_loop:speech.recognition.unknown') LOG.error(e) LOG.error("Speech Recognition could not understand audio") return text
def handle_send(self, message): ''' mycroft wants to send a message to a node instance ''' # send message to client LOG.info("sending") msg = message.data.get("payload") is_file = message.data.get("isBinary", False) peer = message.data.get("peer") ident = message.context.get("ident") if not peer and ident: name = ident.split(":")[0] peer = ":".join(ident.split(":")[1:]) elif peer and ":" not in peer: # name provided peer = self.factory.get_peer_by_name(peer) if not len(peer): peer = None else: peer = peer[0] if self.factory is None: LOG.error("factory not ready") return try: if is_file: # TODO send file self.bus.emit(message.reply("node_red.send.error", { "error": "binary files not supported", "peer": peer, "payload": msg})) elif peer is None: # send message to client self.factory.broadcast_message(msg) self.bus.emit(message.reply("node_red.send.broadcast", {"peer": peer, "payload": msg})) else: # send message to client if self.factory.send_message(peer, msg): self.bus.emit(message.reply("node_red.send.success", {"peer": peer, "payload": msg})) else: LOG.error("That client is not connected") self.bus.emit(message.reply("node_red.send.error", {"error": "unknown error", "peer": peer, "payload": msg})) except Exception as e: LOG.error(e)
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 load_services(config, ws, path=None): """ Search though the service directory and load any services. Args: config: configuration dicrt for the audio backends. ws: websocket object for communication. Returns: List of started services. """ LOG.info("Loading services") if path is None: path = dirname(abspath(__file__)) + '/services/' service_directories = get_services(path) service = [] for descriptor in service_directories: LOG.info('Loading ' + descriptor['name']) try: service_module = imp.load_module(descriptor["name"] + MAINMODULE, *descriptor["info"]) except (KeyboardInterrupt, SystemExit): raise except Exception: LOG.error('Failed to import module ' + descriptor['name'], exc_info=True) if (hasattr(service_module, 'autodetect') and callable(service_module.autodetect)): try: s = service_module.autodetect(config, ws) service += s except (KeyboardInterrupt, SystemExit): raise except Exception: LOG.error('Failed to autodetect...', exc_info=True) if hasattr(service_module, 'load_service'): try: s = service_module.load_service(config, ws) service += s except (KeyboardInterrupt, SystemExit): raise except Exception: LOG.error('Failed to load service...', exc_info=True) return service
def load_services(config, bus, path=None): """ Search though the service directory and load any services. Args: config: configuration dict for the audio backends. bus: Mycroft messagebus Returns: List of started services. """ if path is None: path = dirname(abspath(__file__)) + '/services/' service_directories = get_services(path) service = [] for descriptor in service_directories: LOG.info('Loading ' + descriptor['name']) try: service_module = descriptor['info']['mod'] spec = descriptor['info']['spec'] module_name = descriptor['info']['module_name'] sys.modules[module_name] = service_module spec.loader.exec_module(service_module) except Exception as e: LOG.error('Failed to import module ' + descriptor['name'] + '\n' + repr(e)) continue if (hasattr(service_module, 'autodetect') and callable(service_module.autodetect)): try: s = service_module.autodetect(config, bus) service += s except Exception as e: LOG.error('Failed to autodetect. ' + repr(e)) if hasattr(service_module, 'load_service'): try: s = service_module.load_service(config, bus) service += s except Exception as e: LOG.error('Failed to load service. ' + repr(e)) return service
def load_services(config, ws, path=None): """ Search though the service directory and load any services. Args: config: configuration dict for the audio backends. ws: websocket object for communication. Returns: List of started services. """ if path is None: path = dirname(abspath(__file__)) + '/services/' service_directories = get_services(path) service = [] for descriptor in service_directories: LOG.info('Loading ' + descriptor['name']) try: service_module = imp.load_module(descriptor["name"] + MAINMODULE, *descriptor["info"]) except Exception as e: LOG.error('Failed to import module ' + descriptor['name'] + '\n' + repr(e)) continue if (hasattr(service_module, 'autodetect') and callable(service_module.autodetect)): try: s = service_module.autodetect(config, ws) service += s except Exception as e: LOG.error('Failed to autodetect. ' + repr(e)) if hasattr(service_module, 'load_service'): try: s = service_module.load_service(config, ws) service += s except Exception as e: LOG.error('Failed to load service. ' + repr(e)) return service