def enable_intent(self, intent_name): """ (Re)Enable a registered intent if it belongs to this skill Args: intent_name: name of the intent to be enabled Returns: bool: True if enabled, False if it wasn't registered """ names = [intent[0] for intent in self.registered_intents] intents = [intent[1] for intent in self.registered_intents] if intent_name in names: intent = intents[names.index(intent_name)] self.registered_intents.remove((intent_name, intent)) if ".intent" in intent_name: self.register_intent_file(intent_name, None) else: intent.name = intent_name self.register_intent(intent, None) LOG.debug('Enabling intent ' + intent_name) return True LOG.error('Could not enable ' + intent_name + ', it hasn\'t been ' 'registered.') return False
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 Exception as e: LOG.error('TTS execution failed ({})'.format(repr(e)))
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 default_shutdown(self): """Parent function called internally to shut down everything. Shuts down known entities and calls skill specific shutdown method. """ try: self.shutdown() except Exception as e: LOG.error('Skill specific shutdown function encountered ' 'an error: {}'.format(repr(e))) # Store settings if exists(self._dir): self.settings.store() self.settings.stop_polling() # removing events self.cancel_all_repeating_events() for e, f in self.events: self.bus.remove(e, f) self.events = [] # Remove reference to wrappers self.bus.emit( Message("detach_skill", {"skill_id": str(self.skill_id) + ":"})) try: self.stop() except: LOG.error("Failed to stop skill: {}".format(self.name), exc_info=True)
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 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 __init__(self, bus, service): FallbackSkill.__init__(self) if not PadatiousService.instance: PadatiousService.instance = self self.config = Configuration.get()['padatious'] self.service = service 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 self.container = IntentContainer(intent_cache) self.bus = bus self.bus.on('padatious:register_intent', self.register_intent) self.bus.on('padatious:register_entity', self.register_entity) self.bus.on('detach_intent', self.handle_detach_intent) self.bus.on('owo.skills.initialized', self.train) self.register_fallback(self.handle_fallback, 5) self.finished_training_event = Event() self.finished_initial_train = False self.train_delay = self.config['train_delay'] self.train_time = get_time() + self.train_delay
def flush(self): while self.alive: try: cmd = self.commands.get() + '\n' self.serial.write(cmd.encode()) 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 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 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 __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_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' 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): start = time.time() chunks = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s', utterance) for chunk in chunks: try: mute_and_speak(chunk, ident) except KeyboardInterrupt: raise except Exception: LOG.error('Error in mute_and_speak', exc_info=True) if (_last_stop_signal > start or check_for_signal('buttonPress')): break else: mute_and_speak(utterance, ident) stopwatch.stop() report_timing(ident, 'speech', stopwatch, { 'utterance': utterance, 'tts': tts.__class__.__name__ })
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 run(self): self.start_async() while self.state.running: try: time.sleep(1) if self._config_hash != hash(str(Configuration().get())): LOG.debug('Config has changed, reloading...') self.reload() except KeyboardInterrupt as e: LOG.error(e) self.stop() raise # Re-raise KeyboardInterrupt
def _play(self, req): """play wav file after saving to tmp Args: req (object): requests object """ if req.status_code == 200: self._save(req.content) play_wav(self.filename).communicate() else: LOG.error('%s Http Error: %s for url: %s' % (req.status_code, req.reason, req.url))
def execute(self, sentence, ident=None): phrases = self.__get_phrases(sentence) if len(phrases) > 0: for req in self.__requests(phrases): try: self.begin_audio() self.__play(req) except Exception as e: LOG.error(e.message) finally: self.end_audio()
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 deactivate_except(self, message): """ Deactivate all skills except the provided. """ try: skill_to_keep = message.data['skill'] LOG.info('DEACTIVATING ALL SKILLS EXCEPT {}'.format(skill_to_keep)) if skill_to_keep in [basename(i) for i in self.loaded_skills]: for skill in self.loaded_skills: if basename(skill) != skill_to_keep: self.__deactivate_skill(skill) else: LOG.info('Couldn\'t find skill') except Exception as e: LOG.error('Error during skill removal, {}'.format(repr(e)))
def _play(self, message=None): """ 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') 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): self.bus.emit(Message('SimpleAudioServicePlay')) else: self._is_playing = False
def read(self): while self.alive: try: data = self.serial.readline()[:-2] if data: try: data_str = data.decode() except UnicodeError as e: data_str = data.decode('utf-8', errors='replace') LOG.warning('Invalid characters in response from ' ' enclosure: {}'.format(repr(e))) self.process(data_str) except Exception as e: LOG.error("Reading error: {0}".format(e))
def _load_settings_meta(self): """ Loads settings metadata from skills path. """ if isfile(self._meta_path): try: with open(self._meta_path) as f: data = json.load(f) return data except Exception as e: LOG.error("Failed to load setting file: " + self._meta_path) LOG.error(repr(e)) return None else: LOG.debug("settingemeta.json does not exist") return None
def _send_settings_meta(self, settings_meta): """ Send settingsmeta.json to the server. Args: settings_meta (dict): dictionary of the current settings meta Returns: dict: uuid, a unique id for the setting meta data """ try: uuid = self._put_metadata(settings_meta) return uuid except Exception as e: LOG.error(e) return None
def report_metric(name, data): """ Report a general metric to the OwO 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 activate_skill(self, message): """ Activate a deactivated skill. """ try: skill = message.data['skill'] if skill == 'all': for s in self.loaded_skills: self.__activate_skill(s) else: for s in self.loaded_skills: if skill in s: skill = s break self.__activate_skill(skill) except Exception as e: LOG.error('Couldn\'t activate skill, {}'.format(repr(e)))
def _delete_metadata(self, uuid): """ Deletes the current skill metadata Args: uuid (str): unique id of the skill """ try: LOG.debug("deleting metadata") self.api.request({ "method": "DELETE", "path": self._api_path + "/{}".format(uuid) }) except Exception as e: LOG.error(e) LOG.error("cannot delete metadata because this" "device is not original uploader of skill")
def _lower_volume(self, message=None): """ Is triggered when OwO starts to speak and reduces the volume. Args: message: message bus message, not used but required """ if self.current: LOG.debug('lowering volume') self.current.lower_volume() self.volume_is_low = True try: if self.pulse_quiet: self.pulse_quiet() except Exception as exc: LOG.error(exc)
def _connect(self, message): LOG.info('Trying to connect to chromecast') casts = pychromecast.get_chromecasts() if self.config is None or 'identifier' not in self.config: LOG.error("Chromecast identifier not found!") return # Can't connect since no id is specified else: identifier = self.config['identifier'] for c in casts: if c.name == identifier: self.cast = c break else: LOG.info('Couldn\'t find chromecast ' + identifier) self.connection_attempts += 1 time.sleep(10) self.bus.emit(Message('ChromecastServiceConnect')) return
def disable_intent(self, intent_name): """ Disable a registered intent if it belongs to this skill Args: intent_name (string): name of the intent to be disabled Returns: bool: True if disabled, False if it wasn't registered """ names = [intent_tuple[0] for intent_tuple in self.registered_intents] if intent_name in names: LOG.debug('Disabling intent ' + intent_name) name = str(self.skill_id) + ':' + intent_name self.bus.emit(Message("detach_intent", {"intent_name": name})) return True LOG.error('Could not disable ' + intent_name + ', it hasn\'t been registered.') return False