1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
|
"""Module for discovering Altruist sensors using zeroconf."""
import asyncio
import logging
from zeroconf import ServiceStateChange, Zeroconf
from aiohttp import ClientError, ClientSession
from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf
from .client import AltruistClient
from .model import AltruistDeviceModel
from .errors import AltruistError
_LOGGER: logging.Logger = logging.getLogger(__name__)
_PENDING_TASKS: set[asyncio.Task] = set()
class AltruistDiscoverer:
"""Discover ESP32 sensors using zeroconf."""
def __init__(self, zeroconf_instance: AsyncZeroconf, session: ClientSession) -> None:
"""Initialize the discoverer.
:param zeroconf_instance: An instance of AsyncZeroconf.
"""
self.zeroconf: Zeroconf = zeroconf_instance.zeroconf
self.session = session
self._devices: list[AltruistDeviceModel] = []
async def get_devices(self) -> list[AltruistDeviceModel]:
"""Discover devices advertising the given service type.
This method waits for a fixed interval to collect available devices.
If no device is discovered, it raises DevicesNotFoundException.
:return: A list of ServiceInfo objects for the discovered devices.
:raises DevicesNotFoundException: If no devices are found.
"""
# Clear previous results.
self._devices.clear()
services = ["_altruist._tcp.local."]
aiobrowser = AsyncServiceBrowser(
self.zeroconf, services, handlers=[self._async_on_service_state_change]
)
await asyncio.sleep(5)
return self._devices
def _async_on_service_state_change(
self, zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange
) -> None:
_LOGGER.debug(f"Service {name} of type {service_type} state changed: {state_change}")
if state_change is not ServiceStateChange.Added:
return
task = asyncio.ensure_future(self._async_add_device(zeroconf, service_type, name))
_PENDING_TASKS.add(task)
task.add_done_callback(_PENDING_TASKS.discard)
async def _async_add_device(self, zeroconf: Zeroconf, service_type: str, name: str) -> None:
info = AsyncServiceInfo(service_type, name)
await info.async_request(zeroconf, 3000)
ip = info.parsed_addresses()[0]
try:
client = await AltruistClient.from_ip_address(self.session, ip)
device = client.device
self._devices.append(device)
_LOGGER.debug(f"Added device: {device}")
except AltruistError:
_LOGGER.warning("Skipping unreachable or malformed device at %s", ip)
|