Add timer support to VoIP (#139763)

This commit is contained in:
Paulus Schoutsen 2025-03-04 09:48:10 -05:00 committed by GitHub
parent 7fb949dff7
commit c51a2317e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 96 additions and 2 deletions

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from datetime import timedelta
from enum import IntFlag from enum import IntFlag
from functools import partial from functools import partial
import io import io
@ -16,7 +17,7 @@ import wave
from voip_utils import SIP_PORT, RtpDatagramProtocol from voip_utils import SIP_PORT, RtpDatagramProtocol
from voip_utils.sip import SipDatagramProtocol, SipEndpoint, get_sip_endpoint from voip_utils.sip import SipDatagramProtocol, SipEndpoint, get_sip_endpoint
from homeassistant.components import tts from homeassistant.components import intent, tts
from homeassistant.components.assist_pipeline import PipelineEvent, PipelineEventType from homeassistant.components.assist_pipeline import PipelineEvent, PipelineEventType
from homeassistant.components.assist_satellite import ( from homeassistant.components.assist_satellite import (
AssistSatelliteAnnouncement, AssistSatelliteAnnouncement,
@ -25,6 +26,7 @@ from homeassistant.components.assist_satellite import (
AssistSatelliteEntityDescription, AssistSatelliteEntityDescription,
AssistSatelliteEntityFeature, AssistSatelliteEntityFeature,
) )
from homeassistant.components.intent import TimerEventType, TimerInfo
from homeassistant.components.network import async_get_source_ip from homeassistant.components.network import async_get_source_ip
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import Context, HomeAssistant, callback from homeassistant.core import Context, HomeAssistant, callback
@ -161,6 +163,13 @@ class VoipAssistSatellite(VoIPEntity, AssistSatelliteEntity, RtpDatagramProtocol
await super().async_added_to_hass() await super().async_added_to_hass()
self.voip_device.protocol = self self.voip_device.protocol = self
assert self.device_entry is not None
self.async_on_remove(
intent.async_register_timer_handler(
self.hass, self.device_entry.id, self.async_handle_timer_event
)
)
async def async_will_remove_from_hass(self) -> None: async def async_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass.""" """Run when entity will be removed from hass."""
await super().async_will_remove_from_hass() await super().async_will_remove_from_hass()
@ -174,6 +183,29 @@ class VoipAssistSatellite(VoIPEntity, AssistSatelliteEntity, RtpDatagramProtocol
"""Get the current satellite configuration.""" """Get the current satellite configuration."""
raise NotImplementedError raise NotImplementedError
@callback
def async_handle_timer_event(
self,
event_type: TimerEventType,
timer_info: TimerInfo,
) -> None:
"""Handle timer event."""
if event_type != TimerEventType.FINISHED:
return
if timer_info.name:
message = f"{timer_info.name} finished"
else:
message = f"{timedelta(seconds=timer_info.created_seconds)} timer finished"
async def announce_message():
announcement = await self._resolve_announcement_media_id(message, None)
await self.async_announce(announcement)
self.config_entry.async_create_background_task(
self.hass, announce_message(), "voip_announce_timer"
)
async def async_set_configuration( async def async_set_configuration(
self, config: AssistSatelliteConfiguration self, config: AssistSatelliteConfiguration
) -> None: ) -> None:

View File

@ -3,7 +3,7 @@
"name": "Voice over IP", "name": "Voice over IP",
"codeowners": ["@balloob", "@synesthesiam"], "codeowners": ["@balloob", "@synesthesiam"],
"config_flow": true, "config_flow": true,
"dependencies": ["assist_pipeline", "assist_satellite", "network"], "dependencies": ["assist_pipeline", "assist_satellite", "intent", "network"],
"documentation": "https://www.home-assistant.io/integrations/voip", "documentation": "https://www.home-assistant.io/integrations/voip",
"iot_class": "local_push", "iot_class": "local_push",
"quality_scale": "internal", "quality_scale": "internal",

View File

@ -0,0 +1,62 @@
"""Test the Assist Satellite platform."""
from unittest.mock import patch
import pytest
from homeassistant.components.voip.devices import VoIPDevice
from homeassistant.core import HomeAssistant
from homeassistant.helpers import intent as intent_helper
@pytest.mark.parametrize(
("intent_args", "message"),
[
(
{},
"0:02:00 timer finished",
),
(
{"name": {"value": "pizza"}},
"pizza finished",
),
],
)
async def test_timer_events(
hass: HomeAssistant, voip_device: VoIPDevice, intent_args: dict, message: str
) -> None:
"""Test for timer events."""
await intent_helper.async_handle(
hass,
"test",
intent_helper.INTENT_START_TIMER,
{
"minutes": {"value": 2},
}
| intent_args,
device_id=voip_device.device_id,
)
with (
patch(
"homeassistant.components.voip.assist_satellite.VoipAssistSatellite._resolve_announcement_media_id",
) as mock_resolve,
patch(
"homeassistant.components.voip.assist_satellite.VoipAssistSatellite.async_announce",
) as mock_announce,
):
await intent_helper.async_handle(
hass,
"test",
intent_helper.INTENT_DECREASE_TIMER,
{
"minutes": {"value": 2},
},
device_id=voip_device.device_id,
)
await hass.async_block_till_done(wait_background_tasks=True)
assert len(mock_resolve.mock_calls) == 1
assert len(mock_announce.mock_calls) == 1
assert mock_resolve.mock_calls[0][1][0] == message