Exemplo n.º 1
0
    async def say(
        self,
        text: str,
        site_id="default",
        session_id="",
        request_id: typing.Optional[str] = None,
        block: bool = True,
    ) -> typing.AsyncIterable[
        typing.Union[
            TtsSay, HotwordToggleOn, HotwordToggleOff, AsrToggleOn, AsrToggleOff
        ]
    ]:
        """Send text to TTS system and wait for reply."""
        finished_event = asyncio.Event()
        finished_id = request_id or str(uuid4())
        self.message_events[TtsSayFinished][finished_id] = finished_event

        # Disable ASR/hotword at site
        yield HotwordToggleOff(site_id=site_id, reason=HotwordToggleReason.TTS_SAY)
        yield AsrToggleOff(site_id=site_id, reason=AsrToggleReason.TTS_SAY)

        # Wait for messages to be delivered
        await asyncio.sleep(self.toggle_delay)

        try:
            # Forward to TTS
            _LOGGER.debug("Say: %s", text)
            yield TtsSay(
                id=finished_id, site_id=site_id, session_id=session_id, text=text
            )

            if block:
                # Wait for finished event
                say_finished_timeout = 10.0
                if self.say_chars_per_second > 0:
                    # Estimate timeout based on text length
                    say_finished_timeout = max(
                        say_finished_timeout, len(text) / self.say_chars_per_second
                    )

                _LOGGER.debug(
                    "Waiting for sayFinished (id=%s, timeout=%s)",
                    finished_id,
                    say_finished_timeout,
                )
                await asyncio.wait_for(
                    finished_event.wait(), timeout=say_finished_timeout
                )
        except asyncio.TimeoutError:
            _LOGGER.warning("Did not receive sayFinished before timeout")
        except Exception:
            _LOGGER.exception("say")
        finally:
            # Wait for audio to finish play
            await asyncio.sleep(self.toggle_delay)

            # Re-enable ASR/hotword at site
            yield HotwordToggleOn(site_id=site_id, reason=HotwordToggleReason.TTS_SAY)
            yield AsrToggleOn(site_id=site_id, reason=AsrToggleReason.TTS_SAY)
Exemplo n.º 2
0
    async def play_wav_data(
        self, wav_bytes: bytes, site_id: typing.Optional[str] = None
    ) -> AudioPlayFinished:
        """Play WAV data through speakers."""
        if self.sound_system == "dummy":
            raise RuntimeError("No audio output system configured")

        site_id = site_id or self.site_id
        request_id = str(uuid4())

        def handle_finished():
            while True:
                _, message = yield

                if (
                    isinstance(message, AudioPlayFinished)
                    and (message.id == request_id)
                ) or isinstance(message, AudioPlayError):
                    return message

        def messages():
            yield (
                AudioPlayBytes(wav_bytes=wav_bytes),
                {"site_id": site_id, "request_id": request_id},
            )

        message_types: typing.List[typing.Type[Message]] = [
            AudioPlayFinished,
            AudioPlayError,
        ]

        # Disable hotword/ASR
        self.publish(
            HotwordToggleOff(site_id=site_id, reason=HotwordToggleReason.PLAY_AUDIO)
        )
        self.publish(AsrToggleOff(site_id=site_id, reason=AsrToggleReason.PLAY_AUDIO))

        try:
            # Expecting only a single result
            result = None
            async for response in self.publish_wait(
                handle_finished(), messages(), message_types
            ):
                result = response

            if isinstance(result, AudioPlayError):
                _LOGGER.error(result)
                raise RuntimeError(result.error)

            assert isinstance(result, AudioPlayFinished)
            return result
        finally:
            # Enable hotword/ASR
            self.publish(
                HotwordToggleOn(site_id=site_id, reason=HotwordToggleReason.PLAY_AUDIO)
            )
            self.publish(
                AsrToggleOn(site_id=site_id, reason=AsrToggleReason.PLAY_AUDIO)
            )
    def on_connect(self, client, userdata, flags, rc):
        """Connected to MQTT broker."""
        try:
            topics = [
                AsrToggleOn.topic(),
                AsrToggleOff.topic(),
                AsrStartListening.topic(),
                AsrStopListening.topic(),
            ]

            if self.audioframe_topics:
                # Specific siteIds
                topics.extend(self.audioframe_topics)
            else:
                # All siteIds
                topics.append(AudioFrame.topic(siteId="+"))

            for topic in topics:
                self.client.subscribe(topic)
                _LOGGER.debug("Subscribed to %s", topic)
        except Exception:
            _LOGGER.exception("on_connect")
Exemplo n.º 4
0
    async def maybe_play_sound(
        self,
        sound_name: str,
        site_id: typing.Optional[str] = None,
        request_id: typing.Optional[str] = None,
        block: bool = True,
    ) -> typing.AsyncIterable[SoundsType]:
        """Play WAV sound through audio out if it exists."""
        if site_id in self.no_sound:
            _LOGGER.debug("Sound is disabled for site %s", site_id)
            return

        site_id = site_id or self.site_id
        sound_path = self.sound_paths.get(sound_name)
        if sound_path:
            if sound_path.is_dir():
                sound_file_paths = [
                    p
                    for p in sound_path.rglob("*")
                    if p.is_file() and (p.suffix in self.sound_suffixes)
                ]
                if not sound_file_paths:
                    _LOGGER.debug("No sound files found in %s", str(sound_path))
                    return

                sound_path = random.choice(sound_file_paths)
            elif not sound_path.is_file():
                _LOGGER.error("Sound does not exist: %s", str(sound_path))
                return

            _LOGGER.debug("Playing sound %s", str(sound_path))

            # Convert to WAV
            wav_bytes = DialogueHermesMqtt.convert_to_wav(sound_path)

            if (self.volume is not None) and (self.volume != 1.0):
                wav_bytes = DialogueHermesMqtt.change_volume(wav_bytes, self.volume)

            # Send messages
            request_id = request_id or str(uuid4())
            finished_event = asyncio.Event()
            finished_id = request_id
            self.message_events[AudioPlayFinished][finished_id] = finished_event

            # Disable ASR/hotword at site
            yield HotwordToggleOff(
                site_id=site_id, reason=HotwordToggleReason.PLAY_AUDIO
            )
            yield AsrToggleOff(site_id=site_id, reason=AsrToggleReason.PLAY_AUDIO)

            # Wait for messages to be delivered
            await asyncio.sleep(self.toggle_delay)

            try:
                yield (
                    AudioPlayBytes(wav_bytes=wav_bytes),
                    {"site_id": site_id, "request_id": request_id},
                )

                # Wait for finished event or WAV duration
                if block:
                    wav_duration = get_wav_duration(wav_bytes)
                    wav_timeout = wav_duration + self.sound_timeout_extra
                    _LOGGER.debug(
                        "Waiting for playFinished (id=%s, timeout=%s)",
                        finished_id,
                        wav_timeout,
                    )
                    await asyncio.wait_for(finished_event.wait(), timeout=wav_timeout)
            except asyncio.TimeoutError:
                _LOGGER.warning("Did not receive sayFinished before timeout")
            except Exception:
                _LOGGER.exception("maybe_play_sound")
            finally:
                # Wait for audio to finish playing
                await asyncio.sleep(self.toggle_delay)

                # Re-enable ASR/hotword at site
                yield HotwordToggleOn(
                    site_id=site_id, reason=HotwordToggleReason.PLAY_AUDIO
                )
                yield AsrToggleOn(site_id=site_id, reason=AsrToggleReason.PLAY_AUDIO)
Exemplo n.º 5
0
def test_asr_toggle_off():
    """Test AsrToggleOff."""
    assert AsrToggleOff.topic() == "hermes/asr/toggleOff"
Exemplo n.º 6
0
    async def maybe_play_sound(
        self,
        sound_name: str,
        site_id: typing.Optional[str] = None,
        request_id: typing.Optional[str] = None,
        block: bool = True,
    ) -> typing.AsyncIterable[SoundsType]:
        """Play WAV sound through audio out if it exists."""
        site_id = site_id or self.site_id
        wav_path = self.sound_paths.get(sound_name)
        if wav_path:
            if not wav_path.is_file():
                _LOGGER.error("WAV does not exist: %s", str(wav_path))
                return

            _LOGGER.debug("Playing WAV %s", str(wav_path))
            wav_bytes = wav_path.read_bytes()

            request_id = request_id or str(uuid4())
            finished_event = asyncio.Event()
            finished_id = request_id
            self.message_events[AudioPlayFinished][
                finished_id] = finished_event

            # Disable ASR/hotword at site
            yield HotwordToggleOff(site_id=site_id,
                                   reason=HotwordToggleReason.PLAY_AUDIO)
            yield AsrToggleOff(site_id=site_id,
                               reason=AsrToggleReason.PLAY_AUDIO)

            # Wait for messages to be delivered
            await asyncio.sleep(self.toggle_delay)

            try:
                yield (
                    AudioPlayBytes(wav_bytes=wav_bytes),
                    {
                        "site_id": site_id,
                        "request_id": request_id
                    },
                )

                # Wait for finished event or WAV duration
                if block:
                    wav_duration = get_wav_duration(wav_bytes)
                    wav_timeout = wav_duration + self.sound_timeout_extra
                    _LOGGER.debug("Waiting for playFinished (timeout=%s)",
                                  wav_timeout)
                    await asyncio.wait_for(finished_event.wait(),
                                           timeout=wav_timeout)
            except asyncio.TimeoutError:
                _LOGGER.warning("Did not receive sayFinished before timeout")
            except Exception:
                _LOGGER.exception("maybe_play_sound")
            finally:
                # Wait for audio to finish playing
                await asyncio.sleep(self.toggle_delay)

                # Re-enable ASR/hotword at site
                yield HotwordToggleOn(site_id=site_id,
                                      reason=HotwordToggleReason.PLAY_AUDIO)
                yield AsrToggleOn(site_id=site_id,
                                  reason=AsrToggleReason.PLAY_AUDIO)