def load_phonemes(self, key): """Load phonemes from cache file. Arguments: Key: Key identifying phoneme cache """ pho_file = os.path.join(self.cache_dir, key + ".pho") if os.path.exists(pho_file): try: with open(pho_file, "r") as cachefile: phonemes = cachefile.read().strip() return phonemes except Exception: LOG.debug("Failed to read .PHO from cache") return None
def __init__(self): path = None # TODO check system config platform and go directly to correct path if it exists paths = [ "/opt/venvs/mycroft-core/lib/python3.7/site-packages/", # mark1/2 "/opt/venvs/mycroft-core/lib/python3.4/site-packages/ ", # old mark1 installs "/home/pi/mycroft-core" # picroft ] for p in paths: p = join(p, "mycroft", "configuration", "mycroft.conf") if isfile(p): path = p super().__init__(path) if not self.path or not isfile(self.path): LOG.warning("mycroft root path not found")
def discover_hivemind(name="JarbasVoiceTerminal", access_key="RESISTENCEisFUTILE", crypto_key="resistanceISfutile"): discovery = LocalDiscovery() headers = HiveMindConnection.get_headers(name, access_key) while True: LOG.info("Scanning...") for node_url in discovery.scan(): LOG.info("Fetching Node data: {url}".format(url=node_url)) node = discovery.nodes[node_url] node.connect(crypto_key=crypto_key, node_type=JarbasVoiceTerminal, headers=headers) sleep(5)
def load_phonemes(self, key): """ Load phonemes from cache file. Args: Key: Key identifying phoneme cache """ pho_file = os.path.join(get_cache_directory("tts"), key + ".pho") if os.path.exists(pho_file): try: with open(pho_file, "r") as cachefile: phonemes = json.load(cachefile) return phonemes except Exception as e: LOG.error("Failed to read .PHO from cache ({})".format(e)) return None
def save_phonemes(self, key, phonemes): """ Cache phonemes Args: key: Hash key for the sentence phonemes: phoneme string to save """ cache_dir = get_cache_directory("tts") pho_file = os.path.join(cache_dir, key + ".pho") try: with open(pho_file, "w") as cachefile: cachefile.write(json.dumps(phonemes)) except Exception: LOG.exception("Failed to write {} to cache".format(pho_file))
def get_skills(self): start = time.time() self._response = None self.waiting = True self.bus.emit( Message("intent.service.skills.get", context={ "destination": "intent_service", "source": "intent_api" })) while self.waiting and time.time() - start <= self.timeout: time.sleep(0.3) if time.time() - start > self.timeout: LOG.error("Intent Service timed out!") return None return self._response["skills"]
def on_mention(self, event): post = event["data"]["post"] post = json.loads(post) sender = event["data"]["sender_name"] msg = post["message"] channel_id = post["channel_id"] user_id = post["user_id"] channel_data = self.driver.channels.get_channel(channel_id) channel_name = channel_data["name"] for tag in self.tags: msg = msg.replace(tag, "") LOG.info("New mention at channel: " + channel_name) LOG.info(sender + " said: " + msg) self.handle_mention(msg, sender, channel_id)
def update_precise(self, precise_config): """Continously try to download precise until successful""" precise_exe = None while not precise_exe: try: precise_exe = self.install_exe(precise_config['dist_url']) except TriggerReload: raise except Exception as e: LOG.error( 'Precise could not be downloaded({})'.format(repr(e))) if exists(self.install_destination): precise_exe = self.install_destination else: # Wait one minute before retrying sleep(60) return precise_exe
def get_intent(self, utterance): """ get best intent for utterance """ start = time.time() self._response = None self.waiting = True self.bus.emit( Message("intent.service.intent.get", {"utterance": utterance}, context={ "destination": "intent_service", "source": "intent_api" })) while self.waiting and time.time() - start <= self.timeout: time.sleep(0.3) if time.time() - start > self.timeout: LOG.error("Intent Service timed out!") return None return self._response["intent"]
def _connect_to_gui(self, msg): # Attempt to connect to the port gui_id = msg.data.get("gui_id") if not gui_id == self.gui_id: # Not us, ignore! return # Create the websocket for GUI communications port = msg.data.get("port") if port: LOG.info("Connecting GUI on " + str(port)) self.gui_ws = get_websocket(host=self.mycroft_ip, port=port, route="/gui") self.gui_ws.on("open", self.on_open) self.gui_ws.on("message", self.on_gui_message) self.gui_ws.run_in_thread()
def handle_fallback(self, message): utt = message.data.get('utterance') LOG.debug(self.engine.name + " fallback attempt: " + utt) if not self.finished_training_event.is_set(): LOG.debug('Waiting for training to finish...') self.finished_training_event.wait() data = self.engine.calc_intent(utt) if data["conf"] < 0.5: return False self.make_active() self.emitter.emit(message.reply(data["name"], data=data)) return True
def create_hotword_engines(self): LOG.info("creating hotword engines") hot_words = self.config_core.get("hotwords", {}) for word in hot_words: data = hot_words[word] if word == self.wakeup_recognizer.key_phrase \ or not data.get("active", True): continue sound = data.get("sound") utterance = data.get("utterance") listen = data.get("listen", False) engine = HotWordFactory.create_hotword(word, lang=self.lang) self.hotword_engines[word] = { "engine": engine, "sound": sound, "utterance": utterance, "listen": listen }
def handle_send(self, message): # send message to client msg = message.data.get("payload") is_file = message.data.get("isBinary") peer = message.data.get("peer") if is_file: # TODO send file pass elif peer in self.clients: # send message to client client = self.clients[peer] payload = Message.serialize(msg) client.sendMessage(payload, False) else: LOG.error("That client is not connected") self.mycroft_send("hive.client.send.error", { "error": "That client is not connected", "peer": peer }, message.context)
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"): super().__init__(key_phrase, config, lang) # Hotword module imports from snowboydecoder import HotwordDetector # Hotword module config module = self.config.get("module") if module != "snowboy": LOG.warning(module + " module does not match with Hotword class " "snowboy") # Hotword params models = self.config.get("models", {}) paths = [] for key in models: paths.append(models[key]) sensitivity = self.config.get("sensitivity", 0.5) self.snowboy = HotwordDetector(paths, sensitivity=[sensitivity] * len(paths)) self.lang = str(lang).lower() self.key_phrase = str(key_phrase).lower()
def initialize(): nonlocal instance, complete try: clazz = HotWordFactory.CLASSES[module] instance = clazz(hotword, config, lang=lang) except TriggerReload: complete.set() sleep(0.5) loop.reload() except NoModelAvailable: LOG.warning('Could not found find model for {} on {}.'.format( hotword, module )) instance = None except Exception: LOG.exception( 'Could not create hotword. Falling back to default.') instance = None complete.set()
def create_self_signed_cert(cert_dir, name="jarbas"): """ If name.crt and name.key don't exist in cert_dir, create a new self-signed cert and key pair and write them into that directory. """ if crypto is None: LOG.error("run pip install pyopenssl") raise ImportError CERT_FILE = name + ".crt" KEY_FILE = name + ".key" cert_path = join(cert_dir, CERT_FILE) key_path = join(cert_dir, KEY_FILE) if not exists(join(cert_dir, CERT_FILE)) \ or not exists(join(cert_dir, KEY_FILE)): # create a key pair k = crypto.PKey() k.generate_key(crypto.TYPE_RSA, 1024) # create a self-signed cert cert = crypto.X509() cert.get_subject().C = "PT" cert.get_subject().ST = "Europe" cert.get_subject().L = "Mountains" cert.get_subject().O = "Jarbas AI" cert.get_subject().OU = "Powered by Mycroft-Core" cert.get_subject().CN = gethostname() cert.set_serial_number(random.randint(0, 2000)) cert.gmtime_adj_notBefore(0) cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) cert.set_issuer(cert.get_subject()) cert.set_pubkey(k) # TODO don't use sha1 cert.sign(k, 'sha1') if not exists(cert_dir): makedirs(cert_dir) open(cert_path, "wt").write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) open(join(cert_dir, KEY_FILE), "wt").write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k)) return cert_path, key_path
def run(self): """Thread main loop. Get audio and extra data from queue and play. The queue messages is a tuple containing snd_type: 'mp3' or 'wav' telling the loop what format the data is in data: path to temporary audio data videmes: list of visemes to display while playing listen: if listening should be triggered at the end of the sentence. Playback of audio is started and the visemes are sent over the bus the loop then wait for the playback process to finish before starting checking the next position in queue. If the queue is empty the tts.end_audio() is called possibly triggering listening. """ while not self._terminated: try: (snd_type, data, visemes, ident, listen) = self.queue.get(timeout=2) if not self._processing_queue: self._processing_queue = True self.tts.begin_audio() if snd_type == 'wav': self.p = play_wav(data) elif snd_type == 'mp3': self.p = play_mp3(data) self.p.communicate() self.p.wait() if self.queue.empty(): self.tts.end_audio() self._processing_queue = False except Empty: pass except Exception as e: LOG.exception(e) if self._processing_queue: self.tts.end_audio() self._processing_queue = False
def get_cli_input(self): while True: if self.waiting: sleep(0.3) continue if self.debug: LOG.debug("waiting for input") if self.color: line = input("\x1b[6;33;40m INPUT: \x1b[0m") else: line = input("INPUT:") self.say(line) msg = {"data": {"utterances": [line], "lang": "en-us"}, "type": "recognizer_loop:utterance", "context": {"source": self.client.peer, "destination": "hive_mind", "platform": platform}} self.send_to_hivemind_bus(msg) self.waiting = True
def on_gui_message(self, payload): try: msg = json.loads(payload) if self.debug: LOG.debug("Msg: " + str(payload)) msg_type = msg.get("type") if msg_type == "mycroft.session.set": skill = msg.get("namespace") self.skill = self.skill or skill data = msg.get("data") if skill not in self.vars: self.vars[skill] = {} for d in data: self.vars[skill][d] = data[d] self.on_new_gui_data(data) elif msg_type == "mycroft.session.list.insert": # Insert new namespace self.skill = msg['data'][0]['skill_id'] self.loaded.insert(0, [self.skill, []]) elif msg_type == "mycroft.gui.list.insert": # Insert a page in an existing namespace self.page = msg['data'][0]['url'] pos = msg.get('position') # TODO sometimes throws IndexError: list index out of range # not invalid json, seems like either pos is out of range or # "mycroft.session.list.insert" message was missed # NOTE: only happened once with wiki skill, cant replicate self.loaded[0][1].insert(pos, self.page) #self.skill = self.loaded[0][0] elif msg_type == "mycroft.session.list.move": # Move the namespace at "pos" to the top of the stack pos = msg.get('from') self.loaded.insert(0, self.loaded.pop(pos)) elif msg_type == "mycroft.session.list.remove": pos = msg.get('position') skill = msg.get("namespace") if self.skill == skill: self.skill = None self.loaded.pop(pos) elif msg_type == "mycroft.events.triggered": # Switch selected page of namespace skill = msg['namespace'] self.skill = self.skill or skill pos = msg['data']['number'] for n in self.loaded: if n[0] == skill: # TODO sometimes pos throws # IndexError: list index out of range # ocasionally happens with weather skill # LOGS: # 05:38:29.363 - __main__:on_gui_message:56 - DEBUG - Msg: {"type": "mycroft.events.triggered", "namespace": "mycroft-weather.mycroftai", "event_name": "page_gained_focus", "data": {"number": 1}} # 05:38:29.364 - __main__:on_gui_message:90 - ERROR - list index out of range self.page = n[1][pos] self._draw_buffer() self.on_message(msg) except Exception as e: if self.debug: LOG.exception(e) LOG.error("Invalid JSON: " + str(payload))
def _skip_wake_word(self): """Check if told programatically to skip the wake word For example when we are in a dialog with the user. """ # TODO: remove startListening signal check in 20.02 if check_for_signal('startListening') or self._listen_triggered: return True # Pressing the Mark 1 button can start recording (unless # it is being used to mean 'stop' instead) if check_for_signal('buttonPress', 1): # give other processes time to consume this signal if # it was meant to be a 'stop' sleep(0.25) if check_for_signal('buttonPress'): # Signal is still here, assume it was intended to # begin recording LOG.debug("Button Pressed, wakeword not needed") return True return False
def unregister_client(self, client, code=3078, reason=u"unregister client request"): """ Remove client from list of managed connections. """ LOG.info("deregistering client: " + str(client.peer)) if client.peer in self.clients.keys(): client_data = self.clients[client.peer] or {} j, ip, sock_num = client.peer.split(":") context = { "user": client_data.get("names", ["unknown_user"])[0], "source": client.peer } self.bus.emit( Message("hive.client.disconnect", { "reason": reason, "ip": ip, "sock": sock_num }, context)) client.sendClose(code, reason) self.clients.pop(client.peer)
async def event_handler(self, event): event = json.loads(event) event_type = event.get("event", "") if event_type == "hello": self.on_connect(event) elif event_type == "status_change": self.on_status_change(event) elif event_type == "typing": self.on_typing(event) elif event_type == "posted": self.on_message(event) elif event_type == "channel_viewed": self.on_viewed(event) elif event_type == "preferences_changed": self.on_preferences_changed(event) elif event_type == "post_deleted": self.on_post_deleted(event) elif event_type == "user_added": self.on_user_added(event) elif event_type == "user_removed": self.on_user_removed(event) else: LOG.debug(event)
def _normalized_numbers(self, sentence): """normalized numbers to word equivalent. Args: sentence (str): setence to speak Returns: stf: normalized sentences to speak """ try: from lingua_franca.format import pronounce_number numbers = re.findall(r'-?\d+', sentence) normalized_num = [ (num, pronounce_number(int(num))) for num in numbers ] for num, norm_num in normalized_num: sentence = sentence.replace(num, norm_num, 1) except TypeError: LOG.exception("type error in mimic2_tts.py _normalized_numbers()") except ImportError: LOG.warning("lingua_franca not installed, can not normalize numbers") return sentence
def activate_layer(self, layer_num): # error check if layer_num < 0 or layer_num > len(self.layers): LOG.error("invalid layer number") return False self.current_layer = layer_num # disable other layers self.disable() # TODO in here we should wait for all intents to be detached # sometimes detach intent from this step comes after register from next # is there some bus signal we can track? sleep(0.3) # enable layer LOG.info("Activating Layer " + str(layer_num)) if layer_num < len(self.layers) and len(self.layers): for intent_name in self.layers[layer_num]: self.enable_intent(intent_name) return True return False
def find_input_device(device_name): """ Find audio input device by name. Arguments: device_name: device name or regex pattern to match Returns: device_index (int) or None if device wasn't found """ LOG.info('Searching for input device: {}'.format(device_name)) LOG.debug('Devices: ') pa = pyaudio.PyAudio() pattern = re.compile(device_name) for device_index in range(pa.get_device_count()): dev = pa.get_device_info_by_index(device_index) LOG.debug(' {}'.format(dev['name'])) if dev['maxInputChannels'] > 0 and pattern.match(dev['name']): LOG.debug(' ^-- matched') return device_index return None
def load_local(self, path): """ Load local json file into self. Args: path (str): file to load """ path = expanduser(path) if exists(path) and isfile(path): try: config = load_commented_json(path) for key in config: self.__setitem__(key, config[key]) LOG.debug("Configuration {} loaded".format(path)) except Exception as e: LOG.error("Error loading configuration '{}'".format(path)) LOG.error(repr(e)) else: LOG.debug("Configuration '{}' not defined, skipping".format(path))
def listen(self, source, bus, stream=None): """Listens for chunks of audio that Mycroft should perform STT on. This will listen continuously for a wake-up-word, then return the audio chunk containing the spoken phrase that comes immediately afterwards. Args: source (AudioSource): Source producing the audio chunks bus (EventEmitter): Emitter for notifications of when recording begins and ends. stream (AudioStreamHandler): Stream target that will receive chunks of the utterance audio while it is being recorded Returns: AudioData: audio with the user's utterance, minus the wake-up-word """ assert isinstance(source, AudioSource), "Source must be an AudioSource" # bytes_per_sec = source.SAMPLE_RATE * source.SAMPLE_WIDTH sec_per_buffer = float(source.CHUNK) / source.SAMPLE_RATE # Every time a new 'listen()' request begins, reset the threshold # used for silence detection. This is as good of a reset point as # any, as we expect the user and Mycroft to not be talking. # NOTE: adjust_for_ambient_noise() doc claims it will stop early if # speech is detected, but there is no code to actually do that. self.adjust_for_ambient_noise(source, 1.0) LOG.debug("Waiting for wake word...") self._wait_until_wake_word(source, sec_per_buffer, bus) self._listen_triggered = False if self._stop_signaled: return LOG.debug("Recording...") bus.emit("recognizer_loop:record_begin") frame_data = self._record_phrase(source, sec_per_buffer, stream) audio_data = self._create_audio_data(frame_data, source) bus.emit("recognizer_loop:record_end") LOG.debug("Thinking...") return audio_data
def get_mixer(self, control="Master"): if self._mixer is None: try: mixer = Mixer(control) except Exception as e: try: mixer = Mixer(control) except Exception as e: try: if control != "Master": LOG.warning("could not allocate requested mixer, " "falling back to 'Master'") mixer = Mixer("Master") else: raise except Exception as e: LOG.error("Couldn't allocate mixer") LOG.exception(e) raise self._mixer = mixer return self.mixer
def run(self): """Start and reload mic and STT handling threads as needed. Wait for KeyboardInterrupt and shutdown cleanly. """ try: self.start_async() except Exception: LOG.exception('Starting producer/consumer threads for listener ' 'failed.') return # Handle reload of consumer / producer if config changes while self.state.running: try: time.sleep(1) except KeyboardInterrupt as e: LOG.error(e) self.stop() raise # Re-raise KeyboardInterrupt except Exception: LOG.exception('Exception in RecognizerLoop') raise
def register_client(self, client, platform=None): """ Add client to list of managed connections. """ platform = platform or "unknown" LOG.info("registering client: " + str(client.peer)) t, ip, sock = client.peer.split(":") # see if ip address is blacklisted if ip in self.ip_list and self.blacklist: LOG.warning("Blacklisted ip tried to connect: " + ip) self.unregister_client(client, reason=u"Blacklisted ip") return # see if ip address is whitelisted elif ip not in self.ip_list and not self.blacklist: LOG.warning("Unknown ip tried to connect: " + ip) # if not whitelisted kick self.unregister_client(client, reason=u"Unknown ip") return self.clients[client.peer] = { "object": client, "status": "connected", "platform": platform }