Example #1
0
 def validate_connection(self):
     try:
         subprocess.call([BIN, '--version'])
     except:
         LOG.info("Failed to find mimic at: " + BIN)
         raise Exception(
             'Mimic was not found. Run install-mimic.sh to install it.')
Example #2
0
 def during_download(self, first_run=False):
     LOG.info('Still downloading executable...')
     if first_run:  # TODO: Localize
         self._snd_msg('mouth.text=Updating listener...')
     if not self.download_complete:
         self.show_download_progress = Timer(30, self.during_download)
         self.show_download_progress.start()
Example #3
0
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'))
Example #4
0
    def load_module(module, hotword, config, lang, loop):
        LOG.info('Loading "{}" wake word via {}'.format(hotword, module))
        instance = None
        complete = Event()

        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()

        Thread(target=initialize, daemon=True).start()
        if not complete.wait(INIT_TIMEOUT):
            LOG.info('{} is taking too long to load'.format(module))
            complete.set()
        return instance
Example #5
0
def handle_utterance(event):
    LOG.info("Utterance: " + str(event['utterances']))
    context = {'client_name': 'OwO_listener'}
    if 'ident' in event:
        ident = event.pop('ident')
        context['ident'] = ident
    bus.emit(Message('recognizer_loop:utterance', event, context))
Example #6
0
    def transcribe(self, audio):
        try:
            # Invoke the STT engine on the audio clip
            text = self.stt.execute(audio).lower().strip()
            LOG.debug("STT: " + text)
            return 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:
                LOG.warning("Access Denied at owo.ai")
                return "pair my device"  # phrase to start the pairing process
            else:
                LOG.error(e.__class__.__name__ + ': ' + str(e))
        except RequestException as e:
            LOG.error(e.__class__.__name__ + ': ' + str(e))
        except Exception as e:
            self.emitter.emit('recognizer_loop:speech.recognition.unknown')
            if isinstance(e, IndexError):
                LOG.info('no words were transcribed')
            else:
                LOG.error(e)
            LOG.error("Speech Recognition could not understand audio")
            return None
        if connected():
            dialog_name = 'backend.down'
        else:
            dialog_name = 'not connected to the internet'
        self.emitter.emit('speak', {'utterance': dialog.get(dialog_name)})
Example #7
0
 def supported_uris(self):
     """ Return supported uris of chromecast. """
     LOG.info("Chromecasts found: " + str(self.cast))
     if self.cast:
         return ['http', 'https']
     else:
         return []
Example #8
0
    def _do_net_check(self):
        # TODO: This should live in the derived Enclosure, e.g. Enclosure_Mark1
        LOG.info("Checking internet connection")
        if not connected():  # and self.conn_monitor is None:
            if has_been_paired():
                # TODO: Enclosure/localization
                self.speak("This unit is not connected to the Internet. "
                           "Either plug in a network cable or hold the "
                           "button on top for two seconds, then select "
                           "wifi from the menu")
            else:
                # Begin the unit startup process, this is the first time it
                # is being run with factory defaults.

                # TODO: This logic should be in Enclosure_Mark1
                # TODO: Enclosure/localization

                # Don't listen to mic during this out-of-box experience
                self.bus.emit(Message("owo.mic.mute"))
                # Setup handler to unmute mic at the end of on boarding
                # i.e. after pairing is complete
                self.bus.once('owo.paired', self._handle_pairing_complete)

                self.speak(owo.dialog.get('owo.intro'))
                wait_while_speaking()
                time.sleep(2)  # a pause sounds better than just jumping in

                # Kick off wifi-setup automatically
                data = {'allow_timeout': False, 'lang': self.lang}
                self.bus.emit(Message('system.wifi.setup', data))
Example #9
0
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)))
Example #10
0
def is_speaking():
    """Determine if Text to Speech is occurring

    Returns:
        bool: True while still speaking
    """
    LOG.info("owo.utils.is_speaking() is depreciated, use "
             "owo.audio.is_speaking() instead.")
    return owo.audio.is_speaking()
Example #11
0
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 preceeding request to speak has time to
    begin.
    """
    LOG.info("owo.utils.wait_while_speaking() is depreciated, use "
             "owo.audio.wait_while_speaking() instead.")
    return owo.audio.wait_while_speaking()
Example #12
0
    def listen(self, source, emitter):
        """Listens for chunks of audio that OwO 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
            emitter (EventEmitter): Emitter for notifications of when recording
                                    begins and ends.

        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 OwO 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)
        if self._stop_signaled:
            return

        LOG.debug("Recording...")
        emitter.emit("recognizer_loop:record_begin")

        # If enabled, play a wave file with a short sound to audibly
        # indicate recording has begun.
        if self.config.get('confirm_listening'):
            file = resolve_resource_file(
                self.config.get('sounds').get('start_listening'))
            if file:
                play_wav(file)

        frame_data = self._record_phrase(source, sec_per_buffer)
        audio_data = self._create_audio_data(frame_data, source)
        emitter.emit("recognizer_loop:record_end")
        if self.save_utterances:
            LOG.info("Recording utterance")
            stamp = str(datetime.datetime.now())
            filename = "/tmp/OwO_utterance%s.wav" % stamp
            with open(filename, 'wb') as filea:
                filea.write(audio_data.get_wav_data())
            LOG.debug("Thinking...")

        return audio_data
Example #13
0
 def on_download(self):
     LOG.info('Downloading Precise executable...')
     if isdir(join(self.folder, 'precise-stream')):
         rmtree(join(self.folder, 'precise-stream'))
     for old_package in glob(join(self.folder, 'precise-engine_*.tar.gz')):
         os.remove(old_package)
     self.download_complete = False
     self.show_download_progress = Timer(5,
                                         self.during_download,
                                         args=[True])
     self.show_download_progress.start()
Example #14
0
 def stop(self):
     """ Stop vlc playback. """
     LOG.info('VLCService Stop')
     if self.player.is_playing():
         # Restore volume if lowered
         self.restore_volume()
         self.clear_list()
         self.list_player.stop()
         return True
     else:
         return False
Example #15
0
def autodetect(config, bus):
    """
        Autodetect chromecasts on the network and create backends for each
    """
    casts = pychromecast.get_chromecasts(timeout=5, tries=2, retry_wait=2)
    ret = []
    for c in casts:
        LOG.info(c.name + " found.")
        ret.append(ChromecastService(config, bus, c.name.lower(), c))

    return ret
Example #16
0
 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
Example #17
0
    def train(self, message=None):
        if message is None:
            single_thread = False
        else:
            single_thread = message.data.get('single_thread', False)
        self.finished_training_event.clear()

        LOG.info('Training... (single_thread={})'.format(single_thread))
        self.container.train(single_thread=single_thread)
        LOG.info('Training complete.')

        self.finished_training_event.set()
        self.finished_initial_train = True
Example #18
0
 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)))
Example #19
0
    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
Example #20
0
    def _hack_check_for_duplicates(self):
        # TEMPORARY HACK:  Look for multiple instance of the
        # OwO-speech-client and/or OwO-skills services, which could
        # happen when upgrading a shipping Mark 1 from release 0.8.17 or
        # before.  When found, force the unit to reboot.
        import psutil

        LOG.info("Hack to check for duplicate service instances")

        count_instances = 0
        needs_reboot = False
        for process in psutil.process_iter():
            if process.cmdline() == ['python2.7',
                                     '/usr/local/bin/OwO-speech-client']:
                count_instances += 1
        if (count_instances > 1):
            LOG.info("Duplicate OwO-speech-client found")
            needs_reboot = True

        count_instances = 0
        for process in psutil.process_iter():
            if process.cmdline() == ['python2.7',
                                     '/usr/local/bin/OwO-skills']:
                count_instances += 1
        if (count_instances > 1):
            LOG.info("Duplicate OwO-skills found")
            needs_reboot = True

        if needs_reboot:
            LOG.info("Hack reboot...")
            self.reader.process("unit.reboot")
            self.bus.emit(Message("enclosure.eyes.spin"))
            self.bus.emit(Message("enclosure.mouth.reset"))
Example #21
0
 def install_model(self, url: str, wake_word: str) -> str:
     model_url = url.format(wake_word=wake_word)
     model_file = join(self.folder, posixpath.basename(model_url))
     try:
         install_package(
             model_url,
             self.folder,
             on_download=lambda: LOG.info('Updated precise model'))
     except HTTPError:
         if isfile(model_file):
             LOG.info("Couldn't find remote model.  Using local file")
         else:
             raise RuntimeError('Failed to download model:', model_url)
     return model_file
Example #22
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 #23
0
    def get():
        """
        get the active session.

        :return: An active session
        """
        config = Configuration.get().get('session')

        with SessionManager.__lock:
            if (not SessionManager.__current_session
                    or SessionManager.__current_session.expired()):
                SessionManager.__current_session = Session(
                    str(uuid4()), expiration_seconds=config.get('ttl', 180))
                LOG.info("New Session Start: " +
                         SessionManager.__current_session.session_id)
            return SessionManager.__current_session
Example #24
0
def curate_cache(directory, min_free_percent=5.0, min_free_disk=50):
    """Clear out the directory if needed

    This assumes all the files in the directory can be deleted as freely

    Args:
        directory (str): directory path that holds cached files
        min_free_percent (float): percentage (0.0-100.0) of drive to keep free,
                                  default is 5% if not specified.
        min_free_disk (float): minimum allowed disk space in MB, default
                               value is 50 MB if not specified.
    """

    # Simpleminded implementation -- keep a certain percentage of the
    # disk available.
    # TODO: Would be easy to add more options, like whitelisted files, etc.
    space = psutil.disk_usage(directory)

    # convert from MB to bytes
    min_free_disk *= 1024 * 1024
    # space.percent = space.used/space.total*100.0
    percent_free = 100.0 - space.percent
    if percent_free < min_free_percent and space.free < min_free_disk:
        LOG.info('Low diskspace detected, cleaning cache')
        # calculate how many bytes we need to delete
        bytes_needed = (min_free_percent - percent_free) / 100.0 * space.total
        bytes_needed = int(bytes_needed + 1.0)

        # get all entries in the directory w/ stats
        entries = (os.path.join(directory, fn) for fn in os.listdir(directory))
        entries = ((os.stat(path), path) for path in entries)

        # leave only regular files, insert modification date
        entries = ((stat[ST_MTIME], stat[ST_SIZE], path)
                   for stat, path in entries if S_ISREG(stat[ST_MODE]))

        # delete files with oldest modification date until space is freed
        space_freed = 0
        for moddate, fsize, path in sorted(entries):
            try:
                os.remove(path)
                space_freed += fsize
            except:
                pass

            if space_freed > bytes_needed:
                return  # deleted enough!
Example #25
0
def main():
    """ Main function. Run when file is invoked. """
    reset_sigint_handler()
    check_for_signal("isSpeaking")
    bus = WebsocketClient()  # Connect to the OwO Messagebus
    Configuration.init(bus)
    speech.init(bus)

    LOG.info("Starting Audio Services")
    bus.on('message', create_echo_function('AUDIO', ['owo.audio.service']))
    audio = AudioService(bus)  # Connect audio service instance to message bus
    create_daemon(bus.run_forever)

    wait_for_exit_signal()

    speech.shutdown()
    audio.shutdown()
Example #26
0
 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
Example #27
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 #28
0
    def play(self, tracks, prefered_service):
        """
            play starts playing the audio on the prefered service if it
            supports the uri. If not the next best backend is found.

            Args:
                tracks: list of tracks to play.
                prefered_service: indecates the service the user prefer to play
                                  the tracks.
        """
        self._stop()

        if isinstance(tracks[0], str):
            uri_type = tracks[0].split(':')[0]
        else:
            uri_type = tracks[0][0].split(':')[0]

        # check if user requested a particular service
        if prefered_service and uri_type in prefered_service.supported_uris():
            selected_service = prefered_service
        # check if default supports the uri
        elif self.default and uri_type in self.default.supported_uris():
            LOG.debug("Usando backend por defecto({})".format(
                self.default.name))
            selected_service = self.default
        else:  # Check if any other service can play the media
            LOG.debug("Buscando los servicios")
            for s in self.service:
                if uri_type in s.supported_uris():
                    LOG.debug("El servicio {} soporta la URI {}".format(
                        s, uri_type))
                    selected_service = s
                    break
            else:
                LOG.info('No se encuentran servicios para uri_type: ' +
                         uri_type)
                return
        if not selected_service.supports_mime_hints:
            tracks = [t[0] if isinstance(t, list) else t for t in tracks]
        selected_service.clear_list()
        selected_service.add_list(tracks)
        selected_service.play()
        self.current = selected_service
Example #29
0
    def _connect(self, message):
        """
            Callback method to connect to mopidy if server is not available
            at startup.
        """
        url = 'http://localhost:6680'
        if self.config is not None:
            url = self.config.get('url', url)
        try:
            self.mopidy = Mopidy(url)
        except:
            if self.connection_attempts < 1:
                LOG.debug('Could not connect to server, will retry quietly')
            self.connection_attempts += 1
            time.sleep(10)
            self.bus.emit(Message('MopidyServiceConnect'))
            return

        LOG.info('Connected to mopidy server')
Example #30
0
def download_subscriber_voices(selected_voice):
    """
        Function to download all premium voices, starting with
        the currently selected if applicable
    """

    def make_executable(dest):
        """ Call back function to make the downloaded file executable. """
        LOG.info('Make executable')
        # make executable
        st = os.stat(dest)
        os.chmod(dest, st.st_mode | stat.S_IEXEC)

    # First download the selected voice if needed
    voice_file = SUBSCRIBER_VOICES.get(selected_voice)
    if voice_file is not None and not exists(voice_file):
        LOG.info('voice doesn\'t exist, downloading')
        url = DeviceApi().get_subscriber_voice_url(selected_voice)
        # Check we got an url
        if url:
            dl = download(url, voice_file, make_executable)
            # Wait for completion
            while not dl.done:
                sleep(1)
        else:
            LOG.debug('{} is not available for this architecture'
                      .format(selected_voice))

    # Download the rest of the subsciber voices as needed
    for voice in SUBSCRIBER_VOICES:
        voice_file = SUBSCRIBER_VOICES[voice]
        if not exists(voice_file):
            url = DeviceApi().get_subscriber_voice_url(voice)
            # Check we got an url
            if url:
                dl = download(url, voice_file, make_executable)
                # Wait for completion
                while not dl.done:
                    sleep(1)
            else:
                LOG.debug('{} is not available for this architecture'
                          .format(voice))