Example #1
0
def simple_cli():
    global bus
    global bSimple
    bSimple = True
    bus = WebsocketClient()  # OwO messagebus connection
    event_thread = Thread(target=connect)
    event_thread.setDaemon(True)
    event_thread.start()
    bus.on('speak', handle_speak)
    try:
        while True:
            # Sleep for a while so all the output that results
            # from the previous command finishes before we print.
            time.sleep(1.5)
            print("Input (Ctrl+C to quit):")
            line = sys.stdin.readline()
            bus.emit(
                Message("recognizer_loop:utterance",
                        {'utterances': [line.strip()]}))
    except KeyboardInterrupt as e:
        # User hit Ctrl+C to quit
        print("")
    except KeyboardInterrupt as e:
        LOG.exception(e)
        event_thread.exit()
        sys.exit()
Example #2
0
    def _poll_skill_settings(self):
        """ If identifier exists for this skill poll to backend to
            request settings and store it if it changes
            TODO: implement as websocket
        """
        original = hash(str(self))
        try:
            if not is_paired():
                pass
            elif not self._complete_intialization:
                self.initialize_remote_settings()
                if not self._complete_intialization:
                    return  # unable to do remote sync
            else:
                self.update_remote()

        except Exception as e:
            LOG.exception('Failed to fetch skill settings: {}'.format(repr(e)))
        finally:
            # Call callback for updated settings
            if self.changed_callback and hash(str(self)) != original:
                self.changed_callback()

        if self._poll_timer:
            self._poll_timer.cancel()

        if not self._is_alive:
            return

        # continues to poll settings every minute
        self._poll_timer = Timer(60, self._poll_skill_settings)
        self._poll_timer.daemon = True
        self._poll_timer.start()
Example #3
0
    def _adapt_intent_match(self, utterances, lang):
        """ Run the Adapt engine to search for an matching intent

        Args:
            utterances (list):  list of utterances
            lang (string):      4 letter ISO language code

        Returns:
            Intent structure, or None if no match was found.
        """
        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(
                    self.engine.determine_intent(
                        normalize(utterance, lang),
                        100,
                        include_tags=True,
                        context_manager=self.context_manager))
                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration:
                # don't show error in log
                continue
            except Exception as e:
                LOG.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            self.update_context(best_intent)
            # update active skills
            skill_id = best_intent['intent_type'].split(":")[0]
            self.add_active_skill(skill_id)
            return best_intent
Example #4
0
def load_skill(skill_descriptor, bus, skill_id, BLACKLISTED_SKILLS=None):
    """ Load skill from skill descriptor.

    Args:
        skill_descriptor: descriptor of skill to load
        bus:              OwO messagebus connection
        skill_id:         id number for skill

    Returns:
        OwOSkill: the loaded skill or None on failure
    """
    BLACKLISTED_SKILLS = BLACKLISTED_SKILLS or []
    path = skill_descriptor["path"]
    name = basename(path)
    LOG.info("ATTEMPTING TO LOAD SKILL: {} with ID {}".format(name, skill_id))
    if name in BLACKLISTED_SKILLS:
        LOG.info("SKILL IS BLACKLISTED " + name)
        return None
    main_file = join(path, MainModule + '.py')
    try:
        with open(main_file, 'rb') as fp:
            skill_module = imp.load_module(name.replace('.',
                                                        '_'), fp, main_file,
                                           ('.py', 'rb', imp.PY_SOURCE))
        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(bus)
            try:
                skill.skill_id = skill_id
                skill.load_data_files(path)
                # Set up intent handlers
                skill._register_decorated()
                skill.initialize()
            except Exception as e:
                # If an exception occurs, make sure to clean up the skill
                skill.default_shutdown()
                raise e

            LOG.info("Loaded " + name)
            # The very first time a skill is run, speak the intro
            first_run = skill.settings.get("__OwO_skill_firstrun", True)
            if first_run:
                LOG.info("First run of " + name)
                skill.settings["__OwO_skill_firstrun"] = False
                skill.settings.store()
                intro = skill.get_intro_message()
                if intro:
                    skill.speak(intro)
            return skill
        else:
            LOG.warning("Module {} does not appear to be skill".format(name))
    except Exception:
        LOG.exception("Failed to load skill: " + name)
    return None
Example #5
0
    def handle_utterance(self, message):
        """ Main entrypoint for handling user utterances with OwO skills

        Monitor the messagebus for 'recognizer_loop:utterance', typically
        generated by a spoken interaction but potentially also from a CLI
        or other method of injecting a 'user utterance' into the system.

        Utterances then work through this sequence to be handled:
        1) Active skills attempt to handle using converse()
        2) Adapt intent handlers
        3) Padatious intent handlers
        4) Other fallbacks

        Args:
            message (Message): The messagebus data
        """
        try:
            # Get language of the utterance
            lang = message.data.get('lang', "en-us")
            utterances = message.data.get('utterances', '')

            stopwatch = Stopwatch()
            with stopwatch:
                # Give active skills an opportunity to handle the utterance
                converse = self._converse(utterances, lang)

                if not converse:
                    # No conversation, use intent system to handle utterance
                    intent = self._adapt_intent_match(utterances, lang)
                    padatious_intent = PadatiousService.instance.calc_intent(
                        utterances[0])

            if converse:
                # Report that converse handled the intent and return
                ident = message.context['ident'] if message.context else None
                report_timing(ident, 'intent_service', stopwatch,
                              {'intent_type': 'converse'})
                return
            elif intent and not (padatious_intent
                                 and padatious_intent.conf >= 0.95):
                # Send the message to the Adapt intent's handler unless
                # Padatious is REALLY sure it was directed at it instead.
                reply = message.reply(intent.get('intent_type'), intent)
            else:
                # Allow fallback system to handle utterance
                # NOTE: Padatious intents are handled this way, too
                reply = message.reply('intent_failure', {
                    'utterance': utterances[0],
                    'lang': lang
                })
            self.bus.emit(reply)
            self.send_metrics(intent, message.context, stopwatch)
        except Exception as e:
            LOG.exception(e)
Example #6
0
 def load_priority(self):
     skills = {skill.name: skill for skill in self.msm.list()}
     for skill_name in PRIORITY_SKILLS:
         skill = skills[skill_name]
         if not skill.is_local:
             try:
                 skill.install()
             except Exception:
                 LOG.exception('Downloading priority skill:' + skill.name)
                 if not skill.is_local:
                     continue
         self._load_or_reload_skill(skill.path)
Example #7
0
    def stop(self):
        """ Tell the manager to shutdown """
        self._stop_event.set()

        # Do a clean shutdown of all skills
        for name, skill_info in self.loaded_skills.items():
            instance = skill_info.get('instance')
            if instance:
                try:
                    instance.default_shutdown()
                except Exception:
                    LOG.exception('Shutting down skill: ' + name)
Example #8
0
 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 Exception:
         LOG.exception(
             'Could not create hotword. Falling back to default.')
         instance = None
     complete.set()
Example #9
0
 def load_spellings(self):
     """Load phonetic spellings of words as dictionary"""
     path = join('text', self.lang, 'phonetic_spellings.txt')
     spellings_file = resolve_resource_file(path)
     if not spellings_file:
         return {}
     try:
         with open(spellings_file) as f:
             lines = filter(bool, f.read().split('\n'))
         lines = [i.split(':') for i in lines]
         return {key.strip(): value.strip() for key, value in lines}
     except ValueError:
         LOG.exception('Failed to load phonetic spellings.')
         return {}
Example #10
0
    def on_message(self, message):
        LOG.debug(message)
        try:
            deserialized_message = Message.deserialize(message)
        except:
            return

        try:
            self.emitter.emit(deserialized_message.type, deserialized_message)
        except Exception as e:
            LOG.exception(e)
            traceback.print_exc(file=sys.stdout)
            pass

        for client in client_connections:
            client.write_message(message)
Example #11
0
 def send_skill_list(self, message=None):
     """
         Send list of loaded skills.
     """
     try:
         info = {}
         for s in self.loaded_skills:
             is_active = (self.loaded_skills[s].get('active', True) and
                          self.loaded_skills[s].get('instance') is not None)
             info[basename(s)] = {
                 'active': is_active,
                 'id': self.loaded_skills[s]['id']
             }
         self.bus.emit(Message('owo.skills.list', data=info))
     except Exception as e:
         LOG.exception(e)
Example #12
0
    def save_phonemes(self, key, phonemes):
        """
            Cache phonemes

            Args:
                key:        Hash key for the sentence
                phonemes:   phoneme string to save
        """

        cache_dir = owo.util.get_cache_directory("tts")
        pho_file = os.path.join(cache_dir, key + ".pho")
        try:
            with open(pho_file, "w") as cachefile:
                cachefile.write(phonemes)
        except Exception:
            LOG.exception("Failed to write {} to cache".format(pho_file))
            pass
Example #13
0
    def _unload_removed(self, paths):
        """ Shutdown removed skills.

            Arguments:
                paths: list of current directories in the skills folder
        """
        paths = [p.rstrip('/') for p in paths]
        skills = self.loaded_skills
        # Find loaded skills that doesn't exist on disk
        removed_skills = [str(s) for s in skills.keys() if str(s) not in paths]
        for s in removed_skills:
            LOG.info('removing {}'.format(s))
            try:
                LOG.debug('Removing: {}'.format(skills[s]))
                skills[s]['instance'].default_shutdown()
            except Exception as e:
                LOG.exception(e)
            self.loaded_skills.pop(s)
Example #14
0
    def _normalized_numbers(self, sentence):
        """normalized numbers to word equivalent.

        Args:
            sentence (str): setence to speak

        Returns:
            stf: normalized sentences to speak
        """
        try:
            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()")
        return sentence
Example #15
0
        def wrapper(message):
            skill_data = {'name': get_handler_name(handler)}
            stopwatch = Stopwatch()
            try:
                message = unmunge_message(message, self.skill_id)
                # Indicate that the skill handler is starting
                if handler_info:
                    # Indicate that the skill handler is starting if requested
                    msg_type = handler_info + '.start'
                    self.bus.emit(message.reply(msg_type, skill_data))

                if once:
                    # Remove registered one-time handler before invoking,
                    # allowing them to re-schedule themselves.
                    self.remove_event(name)

                with stopwatch:
                    if len(signature(handler).parameters) == 0:
                        handler()
                    else:
                        handler(message)
                    self.settings.store()  # Store settings if they've changed

            except Exception as e:
                # Convert "MyFancySkill" to "My Fancy Skill" for speaking
                handler_name = camel_case_split(self.name)
                msg_data = {'skill': handler_name}
                msg = dialog.get('skill.error', self.lang, msg_data)
                self.speak(msg)
                LOG.exception(msg)
                # append exception information in message
                skill_data['exception'] = repr(e)
            finally:
                # Indicate that the skill handler has completed
                if handler_info:
                    msg_type = handler_info + '.complete'
                    self.bus.emit(message.reply(msg_type, skill_data))

                # Send timing metrics
                context = message.context
                if context and 'ident' in context:
                    report_timing(context['ident'], 'skill_handler', stopwatch,
                                  {'handler': handler.__name__})
Example #16
0
        def handler(message):
            # indicate fallback handling start
            bus.emit(
                message.reply("owo.skill.handler.start",
                              data={'handler': "fallback"}))

            stopwatch = Stopwatch()
            handler_name = None
            with stopwatch:
                for _, handler in sorted(cls.fallback_handlers.items(),
                                         key=operator.itemgetter(0)):
                    try:
                        if handler(message):
                            #  indicate completion
                            handler_name = get_handler_name(handler)
                            bus.emit(
                                message.reply('owo.skill.handler.complete',
                                              data={
                                                  'handler': "fallback",
                                                  "fallback_handler":
                                                  handler_name
                                              }))
                            break
                    except Exception:
                        LOG.exception('Exception in fallback.')
                else:  # No fallback could handle the utterance
                    bus.emit(message.reply('complete_intent_failure'))
                    warning = "No fallback could handle intent."
                    LOG.warning(warning)
                    #  indicate completion with exception
                    bus.emit(
                        message.reply('owo.skill.handler.complete',
                                      data={
                                          'handler': "fallback",
                                          'exception': warning
                                      }))

            # Send timing metric
            if message.context and message.context['ident']:
                ident = message.context['ident']
                report_timing(ident, 'fallback_handler', stopwatch,
                              {'handler': handler_name})
Example #17
0
    def run(self):
        """
            Thread main loop. get audio and visime data from queue
            and play.
        """
        while not self._terminated:
            try:
                snd_type, data, visimes, ident = self.queue.get(timeout=2)
                self.blink(0.5)
                if not self._processing_queue:
                    self._processing_queue = True
                    self.tts.begin_audio()

                stopwatch = Stopwatch()
                with stopwatch:
                    if snd_type == 'wav':
                        self.p = play_wav(data)
                    elif snd_type == 'mp3':
                        self.p = play_mp3(data)

                    if visimes:
                        if self.show_visimes(visimes):
                            self.clear_queue()
                    else:
                        self.p.communicate()
                    self.p.wait()
                send_playback_metric(stopwatch, ident)

                if self.queue.empty():
                    self.tts.end_audio()
                    self._processing_queue = False
                    self._clear_visimes = False
                self.blink(0.2)
            except Empty:
                pass
            except Exception as e:
                LOG.exception(e)
                if self._processing_queue:
                    self.tts.end_audio()
                    self._processing_queue = False
Example #18
0
    def handle_converse_request(self, message):
        """ Check if the targeted skill id can handle conversation

        If supported, the conversation is invoked.
        """

        skill_id = 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]["instance"]
                    and 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.bus.emit(
                        message.reply("skill.converse.response", {
                            "skill_id": 0,
                            "result": False
                        }))
                    return
                try:
                    result = instance.converse(utterances, lang)
                    self.bus.emit(
                        message.reply("skill.converse.response", {
                            "skill_id": skill_id,
                            "result": result
                        }))
                    return
                except BaseException:
                    LOG.exception("Error in converse method for skill " +
                                  str(skill_id))
        self.bus.emit(
            message.reply("skill.converse.response", {
                "skill_id": 0,
                "result": False
            }))
Example #19
0
    def on_error(self, ws, 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("WS Client will reconnect in %d seconds." % self.retry)
        time.sleep(self.retry)
        self.retry = min(self.retry * 2, 60)
        try:
            self.client = self.create_client()
            self.run_forever()
        except WebSocketException:
            pass
Example #20
0
    def _load_or_reload_skill(self, skill_path):
        """
            Check if unloaded skill or changed skill needs reloading
            and perform loading if necessary.

            Returns True if the skill was loaded/reloaded
        """
        skill_path = skill_path.rstrip('/')
        skill = self.loaded_skills.setdefault(skill_path, {})
        skill.update({"id": basename(skill_path), "path": skill_path})

        # check if folder is a skill (must have __init__.py)
        if not MainModule + ".py" in os.listdir(skill_path):
            return False

        # getting the newest modified date of skill
        modified = _get_last_modified_date(skill_path)
        last_mod = skill.get("last_modified", 0)

        # checking if skill is loaded and hasn't been modified on disk
        if skill.get("loaded") and modified <= last_mod:
            return False  # Nothing to do!

        # check if skill was modified
        elif skill.get("instance") and modified > last_mod:
            # check if skill has been blocked from reloading
            if (not skill["instance"].reload_skill
                    or not skill.get('active', True)):
                return False

            LOG.debug("Reloading Skill: " + basename(skill_path))
            # removing listeners and stopping threads
            try:
                skill["instance"].default_shutdown()
            except Exception:
                LOG.exception("An error occured while shutting down {}".format(
                    skill["instance"].name))

            if DEBUG:
                gc.collect()  # Collect garbage to remove false references
                # Remove two local references that are known
                refs = sys.getrefcount(skill["instance"]) - 2
                if refs > 0:
                    msg = ("After shutdown of {} there are still "
                           "{} references remaining. The skill "
                           "won't be cleaned from memory.")
                    LOG.warning(msg.format(skill['instance'].name, refs))
            del skill["instance"]
            self.bus.emit(
                Message("owo.skills.shutdown", {
                    "path": skill_path,
                    "id": skill["id"]
                }))

        skill["loaded"] = True
        desc = create_skill_descriptor(skill_path)
        skill["instance"] = load_skill(desc, self.bus, skill["id"],
                                       BLACKLISTED_SKILLS)

        skill["last_modified"] = modified
        if skill['instance'] is not None:
            self.bus.emit(
                Message(
                    'owo.skills.loaded', {
                        'path': skill_path,
                        'id': skill['id'],
                        'name': skill['instance'].name,
                        'modified': modified
                    }))
            return True
        else:
            self.bus.emit(
                Message('owo.skills.loading_failure', {
                    'path': skill_path,
                    'id': skill['id']
                }))
        return False