def _load_config(self): """ Load configuration parameters from configuration and initialize self.microphone, self.responsive_recognizer """ # self.config_core = self._init_config_core or Configuration.get() self.config_core = Configuration.get() self.config = self.config_core.get('listener') self._config_hash = recognizer_conf_hash(self.config_core) self.lang = self.config_core.get('lang') rate = self.config.get('sample_rate') device_index = self.config.get('device_index') or \ self.config.get("dev_index") device_name = self.config.get('device_name') if not device_index and device_name: device_index = find_input_device(device_name) LOG.debug('Using microphone (None = default): ' + str(device_index)) self.microphone = MutableMicrophone(device_index, rate, mute=self.mute_calls > 0) self.create_hotword_engines() self.state = RecognizerLoopState() self.responsive_recognizer = NeonResponsiveRecognizer(self)
def update_profile(self, new_preferences: dict, message: Message = None): """ Updates a user profile with the passed new_preferences :param new_preferences: dict of updated preference values. Should follow {section: {key: val}} format :param message: Message associated with request """ if self.server: nick = get_message_user(message) if message else None new_skills_prefs = new_preferences.pop("skills") old_skills_prefs = message.context["nick_profiles"][nick]["skills"] combined_skill_prefs = {**old_skills_prefs, **new_skills_prefs} combined_changes = {k: v for dic in new_preferences.values() for k, v in dic.items()} if new_skills_prefs: combined_changes["skill_settings"] = json.dumps(list(combined_skill_prefs.values())) new_preferences["skills"] = combined_skill_prefs LOG.debug(f"combined_skill_prefs={combined_skill_prefs}") combined_changes["username"] = nick self.socket_emit_to_server("update profile", ["skill", combined_changes, message.context["klat_data"]["request_id"]]) self.bus.emit(Message("neon.remove_cache_entry", {"nick": nick})) old_preferences = message.context["nick_profiles"][nick] message.context["nick_profiles"][nick] = {**old_preferences, **new_preferences} else: for section, settings in new_preferences: # section in user, brands, units, etc. for key, val in settings: self.user_config[section][key] = val self.user_config.write_changes()
def transcribe(self, audio, lang): def send_unknown_intent(): """ Send message that nothing was transcribed. """ self.loop.emit('recognizer_loop:speech.recognition.unknown') try: # Invoke the STT engine on the audio clip try: transcriptions = self.loop.stt.execute(audio, language=lang) except Exception as e: if self.loop.fallback_stt: LOG.warning(f"Using fallback STT, main plugin failed: {e}") transcriptions = self.loop.fallback_stt.execute( audio, language=lang) else: raise e if isinstance(transcriptions, str): LOG.info("Casting str transcriptions to list") transcriptions = [transcriptions] if transcriptions is not None: transcriptions = [t.lower().strip() for t in transcriptions] LOG.debug(f"STT: {transcriptions}") else: send_unknown_intent() LOG.info('no words were transcribed') return transcriptions except Exception as e: send_unknown_intent() LOG.error(e) LOG.exception("Speech Recognition could not understand audio") return None
def speak_dialog(self, key, data=None, expect_response=False, wait=False, message=None, private=False, speaker=None): """ Speak a random sentence from a dialog file. Arguments: :param key: dialog file key (e.g. "hello" to speak from the file "locale/en-us/hello.dialog") :param data: information used to populate key :param expect_response: set to True if Mycroft should listen for a response immediately after speaking. :param wait: set to True to block while the text is being spoken. :param speaker: optional dict of speaker info to use :param private: private flag (server use only) :param message: associated message from request """ data = data or {} LOG.debug(f"data={data}") self.speak( self.dialog_renderer.render( key, data), # TODO: Pass index here to use non-random responses DM expect_response, message=message, private=private, speaker=speaker, wait=wait, meta={ 'dialog': key, 'data': data })
def await_confirmation(self, user, actions, timeout=None): """ Used to add an action for which to await a response (note: this will disable skill reload when called and enable on timeout) :param user: username ("local" for non-server) :param actions: string action name (or list of action names) we are confirming, handled in skill's converse method :param timeout: duration to wait in seconds before removing the action from the list """ from datetime import datetime as dt, timedelta self.reload_skill = False if isinstance(actions, str): actions = [actions] self.actions_to_confirm[user] = actions if not timeout: timeout = self.default_intent_timeout expiration = dt.now(self.sys_tz) + timedelta(seconds=timeout) self.cancel_scheduled_event(user) time.sleep(1) self.schedule_event(self._confirmation_timeout, to_system_time(expiration), data={ "user": user, "action": actions }, name=user) LOG.debug(f"Scheduled {user}")
def neon_in_request(self, message): """ Checks if the utterance is intended for Neon. Server utilizes current conversation, otherwise wake-word status and message "Neon" parameter used """ if message.context.get("neon_should_respond", False): return True elif message.data.get("Neon") or message.data.get("neon"): return True elif not self.server and self.local_config.get("interface", {}).get( "wake_word_enabled", True): return True elif self.server and message.context.get( "klat_data", {}).get("title").startswith("!PRIVATE"): return True else: try: voc_match = self.voc_match(message.data.get("utterance"), "neon") if voc_match: return True except FileNotFoundError: LOG.error(f"No neon vocab found!") if "neon" in message.data.get("utterance").lower(): return True LOG.debug("No Neon") return False
def preference_skill(self, message=None) -> dict: """ Returns the skill settings configuration Equivalent to self.settings for non-server :param message: Message associated with request :return: dict of skill preferences """ nick = get_message_user(message) if message else None if self.server and nick: try: skill = self.skill_id LOG.info(f"Get server prefs for skill={skill}") user_overrides = message.context["nick_profiles"][nick][ "skills"].get(self.skill_id, dict()) LOG.debug(user_overrides) merged_settings = {**self.settings, **user_overrides} if user_overrides.keys() != merged_settings.keys(): LOG.info( f"New settings keys: user={nick}|skill={self.skill_id}|user={user_overrides}" ) self.update_skill_settings(merged_settings, message) return merged_settings except Exception as e: LOG.error(e) return self.settings
def neon_must_respond(self, message): """ Checks if Neon must respond to an utterance (i.e. a server request) @param message: @return: """ if self.server: title = message.context.get("klat_data", {}).get("title", "") LOG.debug(message.data.get("utterance")) if message.data.get("utterance").startswith("Welcome to your private conversation"): return False if title.startswith("!PRIVATE:"): if ',' in title: users = title.split(':')[1].split(',') for idx, val in enumerate(users): users[idx] = val.strip() if len(users) == 2 and "Neon" in users: # Private with Neon # LOG.debug("DM: Private Conversation with Neon") return True elif message.data.get("utterance").lower().startsWith("neon"): # Message starts with "neon", must respond return True else: # Solo Private return True return False
def converse(self, message=None): user = self.get_utterance_user(message) LOG.debug(self.actions_to_confirm) if user in self.actions_to_confirm.keys(): result = self.check_yes_no_response(message) if result == -1: # This isn't a response, ignore it return False elif not result: # User said no if self.local_config.get("interface", {}).get("wake_word_enabled", True): self.speak_dialog("HowAboutMore", expect_response=True) self.enable_intent('CaffeineContentGoodbyeIntent') self.request_check_timeout(self.default_intent_timeout, 'CaffeineContentGoodbyeIntent') else: self.speak_dialog("StayCaffeinated") return True elif result: # User said yes LOG.info(self.results) self._get_drink_text(message) # self.speak(self._get_drink_text()) # self.speak("Provided by CaffeineWiz.") self.speak("Provided by CaffeineWiz. Stay caffeinated!") return True return False
def create(module=None): module = module or "amazon" config = get_neon_lang_config() module = module or config.get("translation_module", "google") # TODO: Check file locations DM if module == "amazon" \ and get_neon_tts_config()["amazon"].get("aws_access_key_id", "") == "": from neon_utils.authentication_utils import find_neon_aws_keys, populate_amazon_keys_config try: config_keys = find_neon_aws_keys() populate_amazon_keys_config(config_keys) except FileNotFoundError: LOG.debug("Amazon credentials not available") module = "google" except Exception as e: LOG.error(e) module = "google" try: clazz = TranslatorFactory.CLASSES.get(module) return clazz() except Exception as e: # The translate backend failed to start. Report it and fall back to # default. LOG.exception( 'The selected translator backend could not be loaded, ' 'falling back to default...') LOG.error(e) if module != 'google': return GoogleTranslator() else: raise
def stub_missing_parameters(skill): global SKILL global TYPE LOG.warning( f"This method is depreciated. Please update your skill to extend NeonSkill instead." ) SKILL = skill TYPE = type(skill) LOG.debug(SKILL) LOG.debug(TYPE) skill.actions_to_confirm = dict() skill.default_intent_timeout = None skill.server = False skill.gui_enabled = is_gui_installed( ) # This isn't a check for running, just available DM skill.neon_in_request = neon_in_request skill.neon_must_respond = neon_must_respond skill.request_from_mobile = request_from_mobile skill.speak_dialog = speak_dialog skill.speak = speak skill.create_signal = create_signal skill.check_for_signal = check_for_signal skill.clear_signals = clear_signals skill.preference_brands = preference_brands skill.preference_user = preference_user skill.preference_location = preference_location skill.preference_unit = preference_unit skill.preference_speech = preference_speech skill.preference_skill = preference_skill skill.build_user_dict = build_user_dict skill.request_check_timeout = request_check_timeout skill.get_utterance_user = get_utterance_user skill.update_skill_settings = update_skill_settings skill.neon_core = False skill.configuration_available = configuration_available try: # TODO: This should really be global to match Neon.. Maybe /opt/mycroft? DM skill.cache_loc = os.path.join(skill.__location__, "cache") except Exception as e: LOG.error(e) skill.cache_loc = os.path.join("tmp", "mycroft", "cache") skill.get_cached_data = get_cached_data skill.update_cached_data = update_cached_data skill.build_message = build_message skill.speak = speak skill.wait_while_speaking = wait_while_speaking skill.is_speaking = is_speaking skill.to_system_time = to_system_time skill.check_yes_no_response = check_yes_no_response
def handle_vol_up(self): self.shadow_volume = self.hardware_volume.get_volume() LOG.debug("Mark2:HardwareEnclosure:handle_vol_up()-was %s" % (self.shadow_volume)) if self.shadow_volume < self.max_volume: self.shadow_volume += self.volume_increment self.hardware_volume.set_volume(self.shadow_volume) self.show_volume(self.shadow_volume)
def build_message(self, kind, utt, message, speaker=None): """ Build a message for user input or neon response :param kind: "neon speak" or "execute" :param utt: string to emit :param message: incoming message object :param speaker: speaker data dictionary :return: Message object """ LOG.debug(speaker) default_speech = self.preference_speech(message) # Override user preference for all script responses if not speaker: speaker = {"name": "Neon", "language": default_speech["tts_language"], "gender": default_speech["tts_gender"], "voice": default_speech["neon_voice"], "override_user": True} elif speaker and speaker.get("language"): speaker["override_user"] = True else: speaker = None LOG.debug(f"data={message.data}") # LOG.debug(f"context={message.context}") emit_response = False if kind == "skill_data": emit_response = True kind = "execute" try: if kind == "execute": # This is picked up in the intent handler return message.reply("skills:execute.utterance", { "utterances": [utt.lower()], "lang": message.data.get("lang", "en-US"), "session": None, "ident": None, "speaker": speaker }, { "neon_should_respond": True, "cc_data": {"request": utt, "emit_response": emit_response, "execute_from_script": True } }) elif kind == "neon speak": added_context = {"cc_data": message.context.get("cc_data", {})} added_context["cc_data"]["request"] = utt return message.reply("speak", {"lang": message.data.get("lang", "en-US"), "speaker": speaker }, added_context) except Exception as x: LOG.error(x)
def handle_action(self): LOG.debug("Mark2:HardwareEnclosure:handle_action()") # debounce this 10 seconds if time.time() - self.last_action > 10: self.last_action = time.time() if self.overide_action is not None: self.overide_action() else: create_signal('buttonPress')
def handle_mute(self, val): LOG.debug("Mark2:HardwareEnclosure:handle_mute() - val = %s" % (val, )) if val != self.last_mute: self.last_mute = val if val == 0: self.leds._set_led_with_brightness(self.mute_led, self.palette.GREEN, 0.5) else: self.leds._set_led_with_brightness(self.mute_led, self.palette.RED, 0.5)
def wait_while_speaking(): """Pause as long as Text to Speech is still happening Pause while Text to Speech is still happening. This always pauses briefly to ensure that any preceding request to speak has time to begin. """ LOG.debug("Wait while speaking!") time.sleep(0.3) # Wait briefly in for any queued speech to begin while is_speaking(): time.sleep(0.1)
def _set_led(self, pixel, color): """ internal interface permits access to the reserved leds """ red_val = color[0] green_val = color[1] blue_val = color[2] cmd = "i2cset -y 1 %d %d %d %d %d i " % (self.device_addr, pixel, red_val, green_val, blue_val) os.system(cmd) LOG.debug("Execute %s" % (cmd, ))
def get_speech_module_config() -> dict: """ Get a dict config with all values required for the speech module read from Neon YML config :returns: dict Mycroft config with Neon YML values where defined """ ovos = get_ovos_config() if "hotwords" in ovos: conf = ovos.pop("hotwords") LOG.debug(f"removed hostwords config: {conf}") neon = get_neon_speech_config() return {**ovos, **neon}
def handle_audio_stream(self, audio, language): short_normalize = (1.0 / 32768.0) swidth = 2 threshold = 10 timeout_length = 5 def rms(frame): count = len(frame) / swidth sum_squares = 0.0 for sample in frame: n = sample * short_normalize sum_squares += n * n rms_value = math.pow(sum_squares / count, 0.5) return rms_value * 1000 stream = self.client.createStream() current_time = time.time() end_time = current_time + timeout_length previous_intermediate_result, current_intermediate_result = '', '' has_data = False for data in audio: data16 = np.frombuffer(data, dtype=np.int16) if data16.max() != data16.min(): has_data = True current_time = time.time() stream.feedAudioContent(data16) current_intermediate_result = stream.intermediateDecode() if rms( data16 ) > threshold and current_intermediate_result != previous_intermediate_result: end_time = current_time + timeout_length previous_intermediate_result = current_intermediate_result if current_time > end_time: break responses = stream.finishStreamWithMetadata(num_results=5) self.transcriptions = [] # LOG.debug(f"The responses are {responses}") for transcript in responses.transcripts: letters = [token.text for token in transcript.tokens] self.transcriptions.append("".join(letters).strip()) # self.transcriptions = responses LOG.debug(self.transcriptions) if has_data: # Model sometimes returns transcripts for absolute silence LOG.debug(f"Audio had data!!") self.text = self.transcriptions[0] else: LOG.warning(f"Audio was empty!") self.text = None self.transcriptions = [] if self.results_event: self.results_event.set() return self.transcriptions
def handle_stop_recording(self, message): background_color = self.m2enc.palette.BLUE foreground_color = self.m2enc.palette.BLACK LOG.debug("Got spoken stuff") if self.pulseLedThread is not None: self.pulseLedThread.exit_flag = True self.pulseLedThread.join() self.pulseLedThread = None if self.chaseLedThread is None: self.chaseLedThread = chaseLedThread(self.m2enc.leds, background_color, foreground_color) self.chaseLedThread.start()
def init_settings(self): """ Initializes yml-based skill config settings, updating from default dict as necessary for added parameters """ if os.path.isfile(os.path.join(self.root_dir, "settingsmeta.yml")): skill_meta = NGIConfig("settingsmeta", self.root_dir).content elif os.path.isfile(os.path.join(self.root_dir, "settingsmeta.json")): with open(os.path.join(self.root_dir, "settingsmeta.json")) as f: skill_meta = json.load(f) else: skill_meta = None # Load defaults from settingsmeta default = {"__mycroft_skill_firstrun": True} if skill_meta: # LOG.info(skill_meta) LOG.info(skill_meta["skillMetadata"]["sections"]) for section in skill_meta["skillMetadata"]["sections"]: for pref in section.get("fields", []): if not pref.get("name"): LOG.debug(f"non-data skill meta: {pref}") else: if pref.get("value") == "true": value = True elif pref.get("value") == "false": value = False elif isinstance(pref.get("value"), CommentedMap): value = dict(pref.get("value")) else: value = pref.get("value") default[pref["name"]] = value # Load or init configuration if os.path.isfile(os.path.join(self.root_dir, f"{self.name}.yml")): LOG.warning( f"Config found in skill directory for {self.name}! Relocating to: {self.file_system.path}" ) shutil.move(os.path.join(self.root_dir, f"{self.name}.yml"), self.file_system.path) self.ngi_settings = NGIConfig(self.name, self.file_system.path) # Load any new or updated keys try: LOG.debug(self.ngi_settings.content) LOG.debug(default) if self.ngi_settings.content and len( self.ngi_settings.content.keys()) > 0 and len( default.keys()) > 0: self.ngi_settings.make_equal_by_keys(default, recursive=False) elif len(default.keys()) > 0: LOG.info("No settings to load, use default") self.ngi_settings.populate(default) except Exception as e: LOG.error(e) self.ngi_settings.populate(default) # Make sure settings is initialized as a dictionary if self.ngi_settings.content: self.settings = self.ngi_settings.content # Uses the default self.settings object for skills compat LOG.debug(f"loaded settings={self.settings}")
def request_check_timeout(self, time_wait, intent_to_check): LOG.info("request received") LOG.info(time_wait) LOG.info(len(intent_to_check)) try: if isinstance(intent_to_check, str): intent_to_check = [intent_to_check] for intent in intent_to_check: data = {'time_out': time_wait, 'intent_to_check': f"{self.skill_id}:{intent}"} LOG.debug(f"DM: Set Timeout: {data}") self.bus.emit(Message("set_timeout", data)) except Exception as x: LOG.error(x)
def run(self): LOG.debug("chase thread started") chase_ctr = 0 while not self.exit_flag: chase_ctr += 1 LOG.error("chase thread %s" % (chase_ctr, )) for x in range(0, 10): self.led_obj.set_led(x, self.fgnd_col) time.sleep(self.delay) self.led_obj.set_led(x, self.bkgnd_col) if chase_ctr > 10: self.exit_flag = True LOG.debug("chase thread stopped") self.led_obj.fill((0, 0, 0))
def update_skill_settings(self, new_preferences: dict, message: Message = None, skill_global=False): """ Updates skill settings with the passed new_preferences :param new_preferences: dict of updated preference values. {key: val} :param message: Message associated with request :param skill_global: Boolean to indicate these are global/non-user-specific variables """ LOG.debug(f"Update skill settings with new: {new_preferences}") if self.server and not skill_global: new_preferences["skill_id"] = self.skill_id self.update_profile({"skills": {self.skill_id: new_preferences}}, message) else: for key, val in new_preferences.items(): self.settings[key] = val self._ngi_settings[key] = val save_settings(self.settings_write_path, self.settings) self._ngi_settings.write_changes()
def _generate_drink_dialog(self, drink: str, message) -> str: """ Generates the dialog and populates self.results for the requested drink :param drink: raw input drink to find :param message: message associated with request :return: generated dialog to speak """ self.results = self._get_matching_drinks(drink) LOG.debug(self.results) if len(self.results) == 1: '''Return the only result''' # self.speak(("The best match is: " + str(self.results[0][0]) + # ", which has " + str(self.results[0][2]) + " milligrams caffeine in " # + str(self.results[0][1])) + " ounces. Provided by CaffeineWiz") drink = str(self.results[0][0]) caff_mg = float(self.results[0][2]) caff_oz = float(self.results[0][1]) else: '''Find the best match from all of the returned results''' matched_drink_names = [ self.results[i][0] for i in range(len(self.results)) ] match = difflib.get_close_matches(drink, matched_drink_names, 1) if match: match2 = [i for i in self.results if i[0] in match] else: match2 = [ i for i in self.results if i[0] in matched_drink_names[0] ] LOG.debug(match) LOG.debug(match2) drink = str(match2[0][0]) caff_mg = float(match2[0][2]) caff_oz = float(match2[0][1]) # self.speak(("The best match is: " + str(match2[0][0]) + # ", which has " + str(match2[0][2]) + " milligrams caffeine in " # + str(match2[0][1])) + " ounces. Provided by CaffeineWiz") preference_unit = self.preference_unit(message) # self.digits = preference_unit['measure'] \ # if preference_unit['measure'] else 'imperial' if preference_unit['measure'] == 'metric': caff_mg, caff_vol, drink_units = self.convert_metric( caff_oz, caff_mg) else: caff_mg = str(caff_mg) caff_vol = str(caff_oz) drink_units = 'ounces' LOG.info(f"{drink} | {caff_mg} | {caff_vol} | {drink_units}") to_speak = self.dialog_renderer.render( 'DrinkCaffeine', { 'drink': drink, 'caffeine_content': caff_mg, 'caffeine_units': self.translate('milligrams'), 'drink_size': caff_vol, 'drink_units': drink_units }) return to_speak
def __handle_send_message_request(self, message): """ Handler for a send message request. This is the entry point for this skill to handle a request. :param message: message generated by Communcation skill """ request_string = message.data["request"] # notify Communcation skill that this skill is working on it self.bus.emit( message.response({ "request": request_string, "skill_id": self.skill_id, "searching": True })) # Calculate match for this skill and respond result: dict = self.CMS_match_message_phrase(request_string, message.context) if result: confidence = self.__calc_confidence(result, result.pop("conf"), message.context) LOG.debug( f"DM: Response from {self.skill_id} ({message.msg_type})") self.bus.emit( message.response({ "request": request_string, "skill_id": self.skill_id, "conf": confidence, "skill_data": result })) else: # Notify not handling request self.bus.emit( message.response({ "request": request_string, "skill_id": self.skill_id, "searching": False }))
def _get_synthesizer(self, language) -> Synthesizer: if '-' in language: language = language.split('-')[0] stopwatch = Stopwatch() with stopwatch: model_name = None for model in self.models: _, lang, dataset, name = model.split('/') print(f"{lang}|{name}") if language in lang: model_name = model if name == self.preferred_model: break model_path, config_path, model_item = self.manager.download_model( model_name) vocoder_name = model_item.get( "default_vocoder", "vocoder_models/universal/libri-tts/fullband-melgan") vocoder_path, vocoder_config_path, _ = self.manager.download_model( vocoder_name) speakers_file_path = '' encoder_path = '' encoder_config_path = '' use_cuda = False synthesizer = Synthesizer( model_path, config_path, speakers_file_path, vocoder_path, vocoder_config_path, encoder_path, encoder_config_path, use_cuda, ) LOG.debug(f"Get synthesizer time={stopwatch.time}") return synthesizer
def _get_links(url): LOG.debug(url) if not str(url).startswith("http"): url = f"http://{url}" LOG.debug(url) html = requests.get(url, timeout=2.0).text soup = BeautifulSoup(html, 'lxml') # LOG.debug(html) # LOG.debug(soup) # Look through the page and find all anchor tags for i in soup.find_all("a", href=True): # LOG.debug(f"DM: found link: {i.text.rstrip()}") # LOG.debug(f"DM: found href: {i['href']}") if '://' not in i['href']: # Assume this is a relative address href = url + i['href'].lower() elif url.split('://')[1] in i['href']: href = i['href'].lower() else: href = None if href: available_links[unicodedata.normalize('NFKD', i.text.rstrip() .replace(u'\u2013', '') .replace(u'\u201d', '') .replace(u'\u201c', '') .replace('"', "") .replace("'", "") .replace("'", "") .lower())] = href LOG.debug("found link: " + unicodedata.normalize("NFKD", i.text.rstrip().replace(u"\u2013", "") .replace(u"\u201d", "").replace(u"\u201c", "") .replace('"', "").replace("'", "") .replace("'", "").replace("\n", "").lower())) LOG.debug("found href: " + href) LOG.debug(available_links)
def check_yes_no_response(self, message): """ Used in converse methods to check if a response confirms or declines an action. Differs from ask_yesno in that this does not assume input will be spoken with wake words enabled :param message: incoming message object to evaluate :return: False if declined, numbers if confirmed numerically, True if confirmed with no numbers """ utterance = message.data.get("utterances")[0] if self.voc_match(utterance, "no"): LOG.info("User Declined") return False elif self.voc_match(utterance, "yes"): LOG.info("User Accepted") numbers = [str(s) for s in utterance.split() if s.isdigit()] if numbers and len(numbers) > 0: confirmation = "".join(numbers) LOG.info(f"Got confirmation: {confirmation}") return confirmation return True else: LOG.debug("User response not valid") return -1
def __init__(self): super(CaffeineWizSkill, self).__init__(name="CaffeineWizSkill") # if skill_needs_patching(self): # LOG.warning("Patching Neon skill for non-neon core") # stub_missing_parameters(self) self.results = None # TODO: Should be dict for multi-user support DM self.translate_drinks = { 'pepsi': 'pepsi cola', # 'coke 0': 'coke zero', 'coke': 'coca-cola classic', 'coca-cola': 'coca-cola classic', 'coca cola': 'coca-cola classic', 'starbucks blonde': 'starbucks coffee blonde roast', 'starbucks blond': 'starbucks coffee blonde roast', 'diet cherry coke': 'diet cherry coca-cola', "a and w root beer": "a&w root beer", "mcdonald's coffee": "mcdonalds coffee", "okay energy drink": "ok energy drink", "vitamin water energy drink": "vitaminwater energy drink", # "7 11 energy shot": "7-eleven energy shot", # "7 up": "7-up", # "amp energy 0": "amp energy zero", "all day energy shot": "allday energy shot", "blue energy drinks": "blu energy drinks", "blue frog energy drink": "blu frog energy drink" } self.last_updated = None try: if self.settings.get("lastUpdate"): self.last_updated = datetime.datetime.strptime( self.settings["lastUpdate"], '%Y-%m-%d %H:%M:%S.%f') except Exception as e: LOG.info(e) LOG.debug(self.last_updated) self.from_caffeine_wiz = None self.from_caffeine_informer = None