128 lines
3.7 KiB
Python
128 lines
3.7 KiB
Python
"""The Voice over IP integration."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
import logging
|
|
|
|
from voip_utils import SIP_PORT
|
|
|
|
from homeassistant.auth.const import GROUP_ID_USER
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import Platform
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import device_registry as dr
|
|
|
|
from .const import CONF_SIP_PORT, DOMAIN
|
|
from .devices import VoIPDevices
|
|
from .voip import HassVoipDatagramProtocol
|
|
|
|
PLATFORMS = (
|
|
Platform.BINARY_SENSOR,
|
|
Platform.SELECT,
|
|
Platform.SWITCH,
|
|
)
|
|
_LOGGER = logging.getLogger(__name__)
|
|
_IP_WILDCARD = "0.0.0.0"
|
|
|
|
__all__ = [
|
|
"DOMAIN",
|
|
"async_setup_entry",
|
|
"async_unload_entry",
|
|
"async_remove_config_entry_device",
|
|
]
|
|
|
|
|
|
@dataclass
|
|
class DomainData:
|
|
"""Domain data."""
|
|
|
|
transport: asyncio.DatagramTransport
|
|
protocol: HassVoipDatagramProtocol
|
|
devices: VoIPDevices
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Set up VoIP integration from a config entry."""
|
|
# Make sure there is a valid user ID for VoIP in the config entry
|
|
if (
|
|
"user" not in entry.data
|
|
or (await hass.auth.async_get_user(entry.data["user"])) is None
|
|
):
|
|
voip_user = await hass.auth.async_create_system_user(
|
|
"Voice over IP", group_ids=[GROUP_ID_USER]
|
|
)
|
|
hass.config_entries.async_update_entry(
|
|
entry, data={**entry.data, "user": voip_user.id}
|
|
)
|
|
|
|
sip_port = entry.options.get(CONF_SIP_PORT, SIP_PORT)
|
|
devices = VoIPDevices(hass, entry)
|
|
devices.async_setup()
|
|
transport, protocol = await _create_sip_server(
|
|
hass,
|
|
lambda: HassVoipDatagramProtocol(hass, devices),
|
|
sip_port,
|
|
)
|
|
_LOGGER.debug("Listening for VoIP calls on port %s", sip_port)
|
|
|
|
hass.data[DOMAIN] = DomainData(transport, protocol, devices)
|
|
|
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
|
|
|
entry.async_on_unload(entry.add_update_listener(update_listener))
|
|
|
|
return True
|
|
|
|
|
|
async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
|
"""Handle options update."""
|
|
await hass.config_entries.async_reload(entry.entry_id)
|
|
|
|
|
|
async def _create_sip_server(
|
|
hass: HomeAssistant,
|
|
protocol_factory: Callable[
|
|
[],
|
|
asyncio.DatagramProtocol,
|
|
],
|
|
sip_port: int,
|
|
) -> tuple[asyncio.DatagramTransport, HassVoipDatagramProtocol]:
|
|
transport, protocol = await hass.loop.create_datagram_endpoint(
|
|
protocol_factory,
|
|
local_addr=(_IP_WILDCARD, sip_port),
|
|
)
|
|
|
|
if not isinstance(protocol, HassVoipDatagramProtocol):
|
|
raise TypeError(f"Expected HassVoipDatagramProtocol, got {protocol}")
|
|
|
|
return transport, protocol
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Unload VoIP."""
|
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
|
_LOGGER.debug("Shutting down VoIP server")
|
|
data = hass.data.pop(DOMAIN)
|
|
data.transport.close()
|
|
await data.protocol.wait_closed()
|
|
_LOGGER.debug("VoIP server shut down successfully")
|
|
|
|
return unload_ok
|
|
|
|
|
|
async def async_remove_config_entry_device(
|
|
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
|
|
) -> bool:
|
|
"""Remove device from a config entry."""
|
|
return True
|
|
|
|
|
|
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
"""Remove VoIP entry."""
|
|
if "user" in entry.data and (
|
|
user := await hass.auth.async_get_user(entry.data["user"])
|
|
):
|
|
await hass.auth.async_remove_user(user)
|