mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
VoIP: Add is active call binary sensor (#91486)
* Refactor VoIP integration for more entities * Add active call binary sensor * Add actually missing binary sensor files * Improve test coverage
This commit is contained in:
parent
58ea657fbc
commit
2b6fd0df6a
@ -17,7 +17,10 @@ from .const import DOMAIN
|
||||
from .devices import VoIPDevices
|
||||
from .voip import HassVoipDatagramProtocol
|
||||
|
||||
PLATFORMS = (Platform.SWITCH,)
|
||||
PLATFORMS = (
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.SWITCH,
|
||||
)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_IP_WILDCARD = "0.0.0.0"
|
||||
|
||||
@ -25,6 +28,7 @@ __all__ = [
|
||||
"DOMAIN",
|
||||
"async_setup_entry",
|
||||
"async_unload_entry",
|
||||
"async_remove_config_entry_device",
|
||||
]
|
||||
|
||||
|
||||
@ -39,6 +43,7 @@ class DomainData:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up VoIP integration from a config entry."""
|
||||
devices = VoIPDevices(hass, entry)
|
||||
devices.async_setup()
|
||||
transport = await _create_sip_server(
|
||||
hass,
|
||||
lambda: HassVoipDatagramProtocol(hass, devices),
|
||||
@ -79,5 +84,5 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_remove_config_entry_device(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
|
||||
) -> bool:
|
||||
"""Remove config entry from a device."""
|
||||
"""Remove device from a config entry."""
|
||||
return True
|
||||
|
60
homeassistant/components/voip/binary_sensor.py
Normal file
60
homeassistant/components/voip/binary_sensor.py
Normal file
@ -0,0 +1,60 @@
|
||||
"""Binary sensor for VoIP."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .devices import VoIPDevice
|
||||
from .entity import VoIPEntity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import DomainData
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up VoIP binary sensor entities."""
|
||||
domain_data: DomainData = hass.data[DOMAIN]
|
||||
|
||||
@callback
|
||||
def async_add_device(device: VoIPDevice) -> None:
|
||||
"""Add device."""
|
||||
async_add_entities([VoIPCallActive(device)])
|
||||
|
||||
domain_data.devices.async_add_new_device_listener(async_add_device)
|
||||
|
||||
async_add_entities([VoIPCallActive(device) for device in domain_data.devices])
|
||||
|
||||
|
||||
class VoIPCallActive(VoIPEntity, BinarySensorEntity):
|
||||
"""Entity to represent voip is allowed."""
|
||||
|
||||
entity_description = BinarySensorEntityDescription(
|
||||
key="call_active",
|
||||
translation_key="call_active",
|
||||
)
|
||||
_attr_is_on = False
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Call when entity about to be added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
self.async_on_remove(self._device.async_listen_update(self._is_active_changed))
|
||||
|
||||
@callback
|
||||
def _is_active_changed(self, device: VoIPDevice) -> None:
|
||||
"""Call when active state changed."""
|
||||
self._attr_is_on = self._device.is_active
|
||||
self.async_write_ha_state()
|
@ -1,17 +1,61 @@
|
||||
"""Class to manage devices."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Iterator
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from voip_utils import CallInfo
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
@dataclass
|
||||
class VoIPDevice:
|
||||
"""Class to store device."""
|
||||
|
||||
voip_id: str
|
||||
device_id: str
|
||||
is_active: bool = False
|
||||
update_listeners: list[Callable[[VoIPDevice], None]] = field(default_factory=list)
|
||||
|
||||
@callback
|
||||
def set_is_active(self, active: bool) -> None:
|
||||
"""Set active state."""
|
||||
self.is_active = active
|
||||
for listener in self.update_listeners:
|
||||
listener(self)
|
||||
|
||||
@callback
|
||||
def async_listen_update(
|
||||
self, listener: Callable[[VoIPDevice], None]
|
||||
) -> Callable[[], None]:
|
||||
"""Listen for updates."""
|
||||
self.update_listeners.append(listener)
|
||||
return lambda: self.update_listeners.remove(listener)
|
||||
|
||||
@callback
|
||||
def async_allow_call(self, hass: HomeAssistant) -> bool:
|
||||
"""Return if call is allowed."""
|
||||
ent_reg = er.async_get(hass)
|
||||
|
||||
allowed_call_entity_id = ent_reg.async_get_entity_id(
|
||||
"switch", DOMAIN, f"{self.voip_id}-allow_call"
|
||||
)
|
||||
# If 2 requests come in fast, the device registry entry has been created
|
||||
# but entity might not exist yet.
|
||||
if allowed_call_entity_id is None:
|
||||
return False
|
||||
|
||||
if state := hass.states.get(allowed_call_entity_id):
|
||||
return state.state == "on"
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class VoIPDevices:
|
||||
"""Class to store devices."""
|
||||
|
||||
@ -19,21 +63,53 @@ class VoIPDevices:
|
||||
"""Initialize VoIP devices."""
|
||||
self.hass = hass
|
||||
self.config_entry = config_entry
|
||||
self._new_device_listeners: list[Callable[[dr.DeviceEntry], None]] = []
|
||||
self._new_device_listeners: list[Callable[[VoIPDevice], None]] = []
|
||||
self.devices: dict[str, VoIPDevice] = {}
|
||||
|
||||
@callback
|
||||
def async_setup(self) -> None:
|
||||
"""Set up devices."""
|
||||
for device in dr.async_entries_for_config_entry(
|
||||
dr.async_get(self.hass), self.config_entry.entry_id
|
||||
):
|
||||
voip_id = next(
|
||||
(item[1] for item in device.identifiers if item[0] == DOMAIN), None
|
||||
)
|
||||
if voip_id is None:
|
||||
continue
|
||||
self.devices[voip_id] = VoIPDevice(
|
||||
voip_id=voip_id,
|
||||
device_id=device.id,
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_device_removed(ev: Event) -> None:
|
||||
"""Handle device removed."""
|
||||
removed_id = ev.data["device_id"]
|
||||
self.devices = {
|
||||
voip_id: voip_device
|
||||
for voip_id, voip_device in self.devices.items()
|
||||
if voip_device.device_id != removed_id
|
||||
}
|
||||
|
||||
self.config_entry.async_on_unload(
|
||||
self.hass.bus.async_listen(
|
||||
dr.EVENT_DEVICE_REGISTRY_UPDATED,
|
||||
async_device_removed,
|
||||
callback(lambda ev: ev.data.get("action") == "remove"),
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_add_new_device_listener(
|
||||
self, listener: Callable[[dr.DeviceEntry], None]
|
||||
self, listener: Callable[[VoIPDevice], None]
|
||||
) -> None:
|
||||
"""Add a new device listener."""
|
||||
self._new_device_listeners.append(listener)
|
||||
|
||||
@callback
|
||||
def async_allow_call(self, call_info: CallInfo) -> bool:
|
||||
"""Check if a call is allowed."""
|
||||
dev_reg = dr.async_get(self.hass)
|
||||
ip_address = call_info.caller_ip
|
||||
|
||||
def async_get_or_create(self, call_info: CallInfo) -> VoIPDevice:
|
||||
"""Get or create a device."""
|
||||
user_agent = call_info.headers.get("user-agent", "")
|
||||
user_agent_parts = user_agent.split()
|
||||
if len(user_agent_parts) == 3 and user_agent_parts[0] == "Grandstream":
|
||||
@ -45,35 +121,34 @@ class VoIPDevices:
|
||||
model = user_agent if user_agent else None
|
||||
fw_version = None
|
||||
|
||||
device = dev_reg.async_get_device({(DOMAIN, ip_address)})
|
||||
dev_reg = dr.async_get(self.hass)
|
||||
voip_id = call_info.caller_ip
|
||||
voip_device = self.devices.get(voip_id)
|
||||
|
||||
if device is None:
|
||||
device = dev_reg.async_get_or_create(
|
||||
config_entry_id=self.config_entry.entry_id,
|
||||
identifiers={(DOMAIN, ip_address)},
|
||||
name=ip_address,
|
||||
manufacturer=manuf,
|
||||
model=model,
|
||||
sw_version=fw_version,
|
||||
)
|
||||
for listener in self._new_device_listeners:
|
||||
listener(device)
|
||||
return False
|
||||
if voip_device is not None:
|
||||
device = dev_reg.async_get(voip_device.device_id)
|
||||
if device and fw_version and device.sw_version != fw_version:
|
||||
dev_reg.async_update_device(device.id, sw_version=fw_version)
|
||||
|
||||
if fw_version is not None and device.sw_version != fw_version:
|
||||
dev_reg.async_update_device(device.id, sw_version=fw_version)
|
||||
return voip_device
|
||||
|
||||
ent_reg = er.async_get(self.hass)
|
||||
|
||||
allowed_call_entity_id = ent_reg.async_get_entity_id(
|
||||
"switch", DOMAIN, f"{ip_address}-allow_call"
|
||||
device = dev_reg.async_get_or_create(
|
||||
config_entry_id=self.config_entry.entry_id,
|
||||
identifiers={(DOMAIN, voip_id)},
|
||||
name=voip_id,
|
||||
manufacturer=manuf,
|
||||
model=model,
|
||||
sw_version=fw_version,
|
||||
)
|
||||
# If 2 requests come in fast, the device registry entry has been created
|
||||
# but entity might not exist yet.
|
||||
if allowed_call_entity_id is None:
|
||||
return False
|
||||
voip_device = self.devices[voip_id] = VoIPDevice(
|
||||
voip_id=voip_id,
|
||||
device_id=device.id,
|
||||
)
|
||||
for listener in self._new_device_listeners:
|
||||
listener(voip_device)
|
||||
|
||||
if state := self.hass.states.get(allowed_call_entity_id):
|
||||
return state.state == "on"
|
||||
return voip_device
|
||||
|
||||
return False
|
||||
def __iter__(self) -> Iterator[VoIPDevice]:
|
||||
"""Iterate over devices."""
|
||||
return iter(self.devices.values())
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.helpers import device_registry as dr, entity
|
||||
from homeassistant.helpers import entity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .devices import VoIPDevice
|
||||
|
||||
|
||||
class VoIPEntity(entity.Entity):
|
||||
@ -13,14 +13,11 @@ class VoIPEntity(entity.Entity):
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
def __init__(self, device: dr.DeviceEntry) -> None:
|
||||
def __init__(self, device: VoIPDevice) -> None:
|
||||
"""Initialize VoIP entity."""
|
||||
ip_address: str = next(
|
||||
item[1] for item in device.identifiers if item[0] == DOMAIN
|
||||
)
|
||||
self._attr_unique_id = f"{ip_address}-{self.entity_description.key}"
|
||||
self._device = device
|
||||
self._attr_unique_id = f"{device.voip_id}-{self.entity_description.key}"
|
||||
self._attr_device_info = entity.DeviceInfo(
|
||||
identifiers={(DOMAIN, ip_address)},
|
||||
identifiers={(DOMAIN, device.voip_id)},
|
||||
)
|
||||
|
@ -15,6 +15,11 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"call_active": {
|
||||
"name": "Call Active"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"allow_call": {
|
||||
"name": "Allow Calls"
|
||||
|
@ -6,12 +6,13 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import STATE_ON
|
||||
from homeassistant.const import STATE_ON, EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr, restore_state
|
||||
from homeassistant.helpers import restore_state
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .devices import VoIPDevice
|
||||
from .entity import VoIPEntity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -27,20 +28,14 @@ async def async_setup_entry(
|
||||
domain_data: DomainData = hass.data[DOMAIN]
|
||||
|
||||
@callback
|
||||
def async_add_device(device: dr.DeviceEntry) -> None:
|
||||
def async_add_device(device: VoIPDevice) -> None:
|
||||
"""Add device."""
|
||||
async_add_entities([VoIPCallAllowedSwitch(device)])
|
||||
|
||||
domain_data.devices.async_add_new_device_listener(async_add_device)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
VoIPCallAllowedSwitch(device)
|
||||
for device in dr.async_entries_for_config_entry(
|
||||
dr.async_get(hass),
|
||||
config_entry.entry_id,
|
||||
)
|
||||
]
|
||||
[VoIPCallAllowedSwitch(device) for device in domain_data.devices]
|
||||
)
|
||||
|
||||
|
||||
@ -48,7 +43,9 @@ class VoIPCallAllowedSwitch(VoIPEntity, restore_state.RestoreEntity, SwitchEntit
|
||||
"""Entity to represent voip is allowed."""
|
||||
|
||||
entity_description = SwitchEntityDescription(
|
||||
key="allow_call", translation_key="allow_call"
|
||||
key="allow_call",
|
||||
translation_key="allow_call",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
|
@ -21,7 +21,7 @@ from homeassistant.const import __version__
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .devices import VoIPDevices
|
||||
from .devices import VoIPDevice, VoIPDevices
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -41,13 +41,16 @@ class HassVoipDatagramProtocol(VoipDatagramProtocol):
|
||||
protocol_factory=lambda call_info: PipelineRtpDatagramProtocol(
|
||||
hass,
|
||||
hass.config.language,
|
||||
devices.async_get_or_create(call_info),
|
||||
),
|
||||
)
|
||||
self.hass = hass
|
||||
self.devices = devices
|
||||
|
||||
def is_valid_call(self, call_info: CallInfo) -> bool:
|
||||
"""Filter calls."""
|
||||
return self.devices.async_allow_call(call_info)
|
||||
device = self.devices.async_get_or_create(call_info)
|
||||
return device.async_allow_call(self.hass)
|
||||
|
||||
|
||||
class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
||||
@ -57,6 +60,7 @@ class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
language: str,
|
||||
voip_device: VoIPDevice,
|
||||
pipeline_timeout: float = 30.0,
|
||||
audio_timeout: float = 2.0,
|
||||
) -> None:
|
||||
@ -66,6 +70,7 @@ class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
||||
|
||||
self.hass = hass
|
||||
self.language = language
|
||||
self.voip_device = voip_device
|
||||
self.pipeline: Pipeline | None = None
|
||||
self.pipeline_timeout = pipeline_timeout
|
||||
self.audio_timeout = audio_timeout
|
||||
@ -76,7 +81,13 @@ class PipelineRtpDatagramProtocol(RtpDatagramProtocol):
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Server is ready."""
|
||||
self.transport = transport
|
||||
super().connection_made(transport)
|
||||
self.voip_device.set_is_active(True)
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""Handle connection is lost or closed."""
|
||||
super().connection_lost(exc)
|
||||
self.voip_device.set_is_active(False)
|
||||
|
||||
def on_chunk(self, audio_bytes: bytes) -> None:
|
||||
"""Handle raw audio chunk."""
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"issues": {
|
||||
"trigger_missing_local_only": {
|
||||
"description": "A choice needs to be made about whether the {webhook_id} webhook automation trigger is accessible from the internet. Edit the {automation_name} automation, and click the gear icon beside the Webhook ID to choose a value for 'Only accessible from the local network'",
|
||||
"description": "A choice needs to be made about whether the {webhook_id} webhook automation trigger is accessible from the internet. [Edit the automation]({edit}) \"{automation_name}\", (`{entity_id}`) and click the gear icon beside the Webhook ID to choose a value for 'Only accessible from the local network'",
|
||||
"title": "Update webhook trigger: {webhook_id}"
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ from unittest.mock import Mock, patch
|
||||
import pytest
|
||||
from voip_utils import CallInfo
|
||||
|
||||
from homeassistant.components.voip import DOMAIN, VoIPDevices
|
||||
from homeassistant.components.voip import DOMAIN
|
||||
from homeassistant.components.voip.devices import VoIPDevice, VoIPDevices
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
@ -62,3 +63,14 @@ def call_info() -> CallInfo:
|
||||
"content-length": "480",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def voip_device(
|
||||
hass: HomeAssistant, voip_devices: VoIPDevices, call_info: CallInfo
|
||||
) -> VoIPDevice:
|
||||
"""Get a VoIP device fixture."""
|
||||
device = voip_devices.async_get_or_create(call_info)
|
||||
# to make sure all platforms are set up
|
||||
await hass.async_block_till_done()
|
||||
return device
|
||||
|
25
tests/components/voip/test_binary_sensor.py
Normal file
25
tests/components/voip/test_binary_sensor.py
Normal file
@ -0,0 +1,25 @@
|
||||
"""Test VoIP binary sensor devices."""
|
||||
from homeassistant.components.voip.devices import VoIPDevice
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
async def test_allow_call(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
voip_device: VoIPDevice,
|
||||
) -> None:
|
||||
"""Test allow call."""
|
||||
state = hass.states.get("binary_sensor.192_168_1_210_call_active")
|
||||
assert state is not None
|
||||
assert state.state == "off"
|
||||
|
||||
voip_device.set_is_active(True)
|
||||
|
||||
state = hass.states.get("binary_sensor.192_168_1_210_call_active")
|
||||
assert state.state == "on"
|
||||
|
||||
voip_device.set_is_active(False)
|
||||
|
||||
state = hass.states.get("binary_sensor.192_168_1_210_call_active")
|
||||
assert state.state == "off"
|
@ -4,7 +4,8 @@ from __future__ import annotations
|
||||
|
||||
from voip_utils import CallInfo
|
||||
|
||||
from homeassistant.components.voip import DOMAIN, VoIPDevices
|
||||
from homeassistant.components.voip import DOMAIN
|
||||
from homeassistant.components.voip.devices import VoIPDevice, VoIPDevices
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceRegistry
|
||||
|
||||
@ -16,7 +17,8 @@ async def test_device_registry_info(
|
||||
device_registry: DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test info in device registry."""
|
||||
assert not voip_devices.async_allow_call(call_info)
|
||||
voip_device = voip_devices.async_get_or_create(call_info)
|
||||
assert not voip_device.async_allow_call(hass)
|
||||
|
||||
device = device_registry.async_get_device({(DOMAIN, call_info.caller_ip)})
|
||||
assert device is not None
|
||||
@ -27,8 +29,9 @@ async def test_device_registry_info(
|
||||
|
||||
# Test we update the device if the fw updates
|
||||
call_info.headers["user-agent"] = "Grandstream HT801 2.0.0.0"
|
||||
voip_device = voip_devices.async_get_or_create(call_info)
|
||||
|
||||
assert not voip_devices.async_allow_call(call_info)
|
||||
assert not voip_device.async_allow_call(hass)
|
||||
|
||||
device = device_registry.async_get_device({(DOMAIN, call_info.caller_ip)})
|
||||
assert device.sw_version == "2.0.0.0"
|
||||
@ -42,9 +45,28 @@ async def test_device_registry_info_from_unknown_phone(
|
||||
) -> None:
|
||||
"""Test info in device registry from unknown phone."""
|
||||
call_info.headers["user-agent"] = "Unknown"
|
||||
assert not voip_devices.async_allow_call(call_info)
|
||||
voip_device = voip_devices.async_get_or_create(call_info)
|
||||
assert not voip_device.async_allow_call(hass)
|
||||
|
||||
device = device_registry.async_get_device({(DOMAIN, call_info.caller_ip)})
|
||||
assert device.manufacturer is None
|
||||
assert device.model == "Unknown"
|
||||
assert device.sw_version is None
|
||||
|
||||
|
||||
async def test_remove_device_registry_entry(
|
||||
hass: HomeAssistant,
|
||||
voip_device: VoIPDevice,
|
||||
voip_devices: VoIPDevices,
|
||||
device_registry: DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test removing a device registry entry."""
|
||||
assert voip_device.voip_id in voip_devices.devices
|
||||
assert hass.states.get("switch.192_168_1_210_allow_calls") is not None
|
||||
|
||||
device_registry.async_remove_device(voip_device.device_id)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("switch.192_168_1_210_allow_calls") is None
|
||||
assert voip_device.voip_id not in voip_devices.devices
|
||||
|
@ -1,9 +1,6 @@
|
||||
"""Test VoIP init."""
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
# socket_enabled,
|
||||
# unused_udp_port_factory,
|
||||
|
||||
|
||||
async def test_unload_entry(
|
||||
hass: HomeAssistant,
|
||||
|
@ -1,16 +1,16 @@
|
||||
"""Test VoIP switch devices."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.voip.devices import VoIPDevice
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
async def test_allow_call(
|
||||
hass: HomeAssistant, config_entry, voip_devices, call_info
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
voip_device: VoIPDevice,
|
||||
) -> None:
|
||||
"""Test allow call."""
|
||||
assert not voip_devices.async_allow_call(call_info)
|
||||
await hass.async_block_till_done()
|
||||
assert not voip_device.async_allow_call(hass)
|
||||
|
||||
state = hass.states.get("switch.192_168_1_210_allow_calls")
|
||||
assert state is not None
|
||||
@ -28,13 +28,25 @@ async def test_allow_call(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert voip_devices.async_allow_call(call_info)
|
||||
assert voip_device.async_allow_call(hass)
|
||||
|
||||
state = hass.states.get("switch.192_168_1_210_allow_calls")
|
||||
assert state.state == "on"
|
||||
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("switch.192_168_1_210_allow_calls")
|
||||
assert state is not None
|
||||
assert state.state == "on"
|
||||
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"turn_off",
|
||||
{"entity_id": "switch.192_168_1_210_allow_calls"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert not voip_device.async_allow_call(hass)
|
||||
|
||||
state = hass.states.get("switch.192_168_1_210_allow_calls")
|
||||
assert state.state == "off"
|
||||
|
@ -5,6 +5,7 @@ from unittest.mock import Mock, patch
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.components import assist_pipeline, voip
|
||||
from homeassistant.components.voip.devices import VoIPDevice
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
@ -12,7 +13,10 @@ _ONE_SECOND = 16000 * 2 # 16Khz 16-bit
|
||||
_MEDIA_ID = "12345"
|
||||
|
||||
|
||||
async def test_pipeline(hass: HomeAssistant) -> None:
|
||||
async def test_pipeline(
|
||||
hass: HomeAssistant,
|
||||
voip_device: VoIPDevice,
|
||||
) -> None:
|
||||
"""Test that pipeline function is called from RTP protocol."""
|
||||
assert await async_setup_component(hass, "voip", {})
|
||||
|
||||
@ -80,8 +84,7 @@ async def test_pipeline(hass: HomeAssistant) -> None:
|
||||
new=async_get_media_source_audio,
|
||||
):
|
||||
rtp_protocol = voip.voip.PipelineRtpDatagramProtocol(
|
||||
hass,
|
||||
hass.config.language,
|
||||
hass, hass.config.language, voip_device
|
||||
)
|
||||
rtp_protocol.transport = Mock()
|
||||
|
||||
@ -108,7 +111,7 @@ async def test_pipeline(hass: HomeAssistant) -> None:
|
||||
await done.wait()
|
||||
|
||||
|
||||
async def test_pipeline_timeout(hass: HomeAssistant) -> None:
|
||||
async def test_pipeline_timeout(hass: HomeAssistant, voip_device: VoIPDevice) -> None:
|
||||
"""Test timeout during pipeline run."""
|
||||
assert await async_setup_component(hass, "voip", {})
|
||||
|
||||
@ -122,7 +125,7 @@ async def test_pipeline_timeout(hass: HomeAssistant) -> None:
|
||||
new=async_pipeline_from_audio_stream,
|
||||
):
|
||||
rtp_protocol = voip.voip.PipelineRtpDatagramProtocol(
|
||||
hass, hass.config.language, pipeline_timeout=0.001
|
||||
hass, hass.config.language, voip_device, pipeline_timeout=0.001
|
||||
)
|
||||
transport = Mock(spec=["close"])
|
||||
rtp_protocol.connection_made(transport)
|
||||
@ -138,7 +141,7 @@ async def test_pipeline_timeout(hass: HomeAssistant) -> None:
|
||||
await done.wait()
|
||||
|
||||
|
||||
async def test_stt_stream_timeout(hass: HomeAssistant) -> None:
|
||||
async def test_stt_stream_timeout(hass: HomeAssistant, voip_device: VoIPDevice) -> None:
|
||||
"""Test timeout in STT stream during pipeline run."""
|
||||
assert await async_setup_component(hass, "voip", {})
|
||||
|
||||
@ -155,7 +158,7 @@ async def test_stt_stream_timeout(hass: HomeAssistant) -> None:
|
||||
new=async_pipeline_from_audio_stream,
|
||||
):
|
||||
rtp_protocol = voip.voip.PipelineRtpDatagramProtocol(
|
||||
hass, hass.config.language, audio_timeout=0.001
|
||||
hass, hass.config.language, voip_device, audio_timeout=0.001
|
||||
)
|
||||
transport = Mock(spec=["close"])
|
||||
rtp_protocol.connection_made(transport)
|
||||
|
Loading…
x
Reference in New Issue
Block a user