Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/discord/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_push",
"loggers": ["discord"],
"requirements": ["nextcord==2.6.0"]
"requirements": ["nextcord==3.1.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/google/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/google",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==7.1.0", "oauth2client==4.1.3", "ical==10.0.0"]
"requirements": ["gcal-sync==7.1.0", "oauth2client==4.1.3", "ical==10.0.4"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/local_calendar/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
"iot_class": "local_polling",
"loggers": ["ical"],
"requirements": ["ical==10.0.0"]
"requirements": ["ical==10.0.4"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/local_todo/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/local_todo",
"iot_class": "local_polling",
"requirements": ["ical==10.0.0"]
"requirements": ["ical==10.0.4"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/remote_calendar/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["ical"],
"quality_scale": "silver",
"requirements": ["ical==10.0.0"]
"requirements": ["ical==10.0.4"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/russound_rio/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"iot_class": "local_push",
"loggers": ["aiorussound"],
"quality_scale": "silver",
"requirements": ["aiorussound==4.5.2"],
"requirements": ["aiorussound==4.6.0"],
"zeroconf": ["_rio._tcp.local."]
}
92 changes: 63 additions & 29 deletions homeassistant/components/wyoming/assist_satellite.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from collections.abc import AsyncGenerator
import io
import logging
import time
from typing import Any, Final
import wave

Expand Down Expand Up @@ -36,6 +37,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.ulid import ulid_now

from .const import DOMAIN, SAMPLE_CHANNELS, SAMPLE_WIDTH
from .data import WyomingService
Expand All @@ -53,6 +55,7 @@
_PIPELINE_FINISH_TIMEOUT: Final = 1
_TTS_SAMPLE_RATE: Final = 22050
_ANNOUNCE_CHUNK_BYTES: Final = 2048 # 1024 samples
_TTS_TIMEOUT_EXTRA: Final = 1.0

# Wyoming stage -> Assist stage
_STAGES: dict[PipelineStage, assist_pipeline.PipelineStage] = {
Expand Down Expand Up @@ -125,6 +128,10 @@ def __init__(
self._ffmpeg_manager: ffmpeg.FFmpegManager | None = None
self._played_event_received: asyncio.Event | None = None

# Randomly set on each pipeline loop run.
# Used to ensure TTS timeout is acted on correctly.
self._run_loop_id: str | None = None

@property
def pipeline_entity_id(self) -> str | None:
"""Return the entity ID of the pipeline to use for the next conversation."""
Expand Down Expand Up @@ -511,6 +518,7 @@ async def _run_pipeline_loop(self) -> None:
wake_word_phrase: str | None = None
run_pipeline: RunPipeline | None = None
send_ping = True
self._run_loop_id = ulid_now()

# Read events and check for pipeline end in parallel
pipeline_ended_task = self.config_entry.async_create_background_task(
Expand Down Expand Up @@ -698,38 +706,52 @@ async def _stream_tts(self, tts_result: tts.ResultStream) -> None:
f"Cannot stream audio format to satellite: {tts_result.extension}"
)

data = b"".join([chunk async for chunk in tts_result.async_stream_result()])

with io.BytesIO(data) as wav_io, wave.open(wav_io, "rb") as wav_file:
sample_rate = wav_file.getframerate()
sample_width = wav_file.getsampwidth()
sample_channels = wav_file.getnchannels()
_LOGGER.debug("Streaming %s TTS sample(s)", wav_file.getnframes())

timestamp = 0
await self._client.write_event(
AudioStart(
rate=sample_rate,
width=sample_width,
channels=sample_channels,
timestamp=timestamp,
).event()
)
# Track the total duration of TTS audio for response timeout
total_seconds = 0.0
start_time = time.monotonic()

# Stream audio chunks
while audio_bytes := wav_file.readframes(_SAMPLES_PER_CHUNK):
chunk = AudioChunk(
rate=sample_rate,
width=sample_width,
channels=sample_channels,
audio=audio_bytes,
timestamp=timestamp,
try:
data = b"".join([chunk async for chunk in tts_result.async_stream_result()])

with io.BytesIO(data) as wav_io, wave.open(wav_io, "rb") as wav_file:
sample_rate = wav_file.getframerate()
sample_width = wav_file.getsampwidth()
sample_channels = wav_file.getnchannels()
_LOGGER.debug("Streaming %s TTS sample(s)", wav_file.getnframes())

timestamp = 0
await self._client.write_event(
AudioStart(
rate=sample_rate,
width=sample_width,
channels=sample_channels,
timestamp=timestamp,
).event()
)
await self._client.write_event(chunk.event())
timestamp += chunk.seconds

await self._client.write_event(AudioStop(timestamp=timestamp).event())
_LOGGER.debug("TTS streaming complete")
# Stream audio chunks
while audio_bytes := wav_file.readframes(_SAMPLES_PER_CHUNK):
chunk = AudioChunk(
rate=sample_rate,
width=sample_width,
channels=sample_channels,
audio=audio_bytes,
timestamp=timestamp,
)
await self._client.write_event(chunk.event())
timestamp += chunk.seconds
total_seconds += chunk.seconds

await self._client.write_event(AudioStop(timestamp=timestamp).event())
_LOGGER.debug("TTS streaming complete")
finally:
send_duration = time.monotonic() - start_time
timeout_seconds = max(0, total_seconds - send_duration + _TTS_TIMEOUT_EXTRA)
self.config_entry.async_create_background_task(
self.hass,
self._tts_timeout(timeout_seconds, self._run_loop_id),
name="wyoming TTS timeout",
)

async def _stt_stream(self) -> AsyncGenerator[bytes]:
"""Yield audio chunks from a queue."""
Expand All @@ -744,6 +766,18 @@ async def _stt_stream(self) -> AsyncGenerator[bytes]:

yield chunk

async def _tts_timeout(
self, timeout_seconds: float, run_loop_id: str | None
) -> None:
"""Force state change to IDLE in case TTS played event isn't received."""
await asyncio.sleep(timeout_seconds + _TTS_TIMEOUT_EXTRA)

if run_loop_id != self._run_loop_id:
# On a different pipeline run now
return

self.tts_response_finished()

@callback
def _handle_timer(
self, event_type: intent.TimerEventType, timer: intent.TimerInfo
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/zwave_js/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,7 @@ class ZWaveDiscoverySchema:
writeable=False,
),
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
# generic text sensors
ZWaveDiscoverySchema(
Expand Down Expand Up @@ -932,6 +933,7 @@ class ZWaveDiscoverySchema:
),
data_template=NumericSensorDataTemplate(),
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
# Meter sensors for Meter CC
ZWaveDiscoverySchema(
Expand All @@ -957,6 +959,7 @@ class ZWaveDiscoverySchema:
writeable=True,
),
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
),
# button for Indicator CC
ZWaveDiscoverySchema(
Expand All @@ -980,6 +983,7 @@ class ZWaveDiscoverySchema:
writeable=True,
),
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
),
# binary switch
# barrier operator signaling states
Expand Down
10 changes: 6 additions & 4 deletions homeassistant/helpers/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,9 +682,12 @@ def _load_services_file(hass: HomeAssistant, integration: Integration) -> JSON_T

def _load_services_files(
hass: HomeAssistant, integrations: Iterable[Integration]
) -> list[JSON_TYPE]:
) -> dict[str, JSON_TYPE]:
"""Load service files for multiple integrations."""
return [_load_services_file(hass, integration) for integration in integrations]
return {
integration.domain: _load_services_file(hass, integration)
for integration in integrations
}


@callback
Expand Down Expand Up @@ -744,10 +747,9 @@ async def async_get_all_descriptions(
_LOGGER.error("Failed to load integration: %s", domain, exc_info=int_or_exc)

if integrations:
contents = await hass.async_add_executor_job(
loaded = await hass.async_add_executor_job(
_load_services_files, hass, integrations
)
loaded = dict(zip(domains_with_missing_services, contents, strict=False))

# Load translations for all service domains
translations = await translation.async_get_translations(
Expand Down
6 changes: 3 additions & 3 deletions requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading