Add multiple NICs in govee_light_local (#128123)

This commit is contained in:
Ian Tewksbury
2025-08-28 02:04:50 -04:00
committed by GitHub
parent 3bdd532dcd
commit f4673f44ee
3 changed files with 83 additions and 37 deletions

View File

@@ -9,6 +9,7 @@ import logging
from govee_local_api.controller import LISTENING_PORT
from homeassistant.components import network
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
@@ -23,12 +24,24 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: GoveeLocalConfigEntry) -> bool:
"""Set up Govee light local from a config entry."""
coordinator = GoveeLocalApiCoordinator(hass, entry)
# Get source IPs for all enabled adapters
source_ips = await network.async_get_enabled_source_ips(hass)
_LOGGER.debug("Enabled source IPs: %s", source_ips)
coordinator: GoveeLocalApiCoordinator = GoveeLocalApiCoordinator(
hass=hass, config_entry=entry, source_ips=source_ips
)
async def await_cleanup():
cleanup_complete: asyncio.Event = coordinator.cleanup()
cleanup_complete_events: [asyncio.Event] = coordinator.cleanup()
with suppress(TimeoutError):
await asyncio.wait_for(cleanup_complete.wait(), 1)
await asyncio.gather(
*[
asyncio.wait_for(cleanup_complete_event.wait(), 1)
for cleanup_complete_event in cleanup_complete_events
]
)
entry.async_on_unload(await_cleanup)

View File

@@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio
from contextlib import suppress
from ipaddress import IPv4Address, IPv6Address
import logging
from govee_local_api import GoveeController
@@ -23,15 +24,13 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
async def _async_has_devices(hass: HomeAssistant) -> bool:
"""Return if there are devices that can be discovered."""
adapter = await network.async_get_source_ip(hass, network.PUBLIC_TARGET_IP)
async def _async_discover(
hass: HomeAssistant, adapter_ip: IPv4Address | IPv6Address
) -> bool:
controller: GoveeController = GoveeController(
loop=hass.loop,
logger=_LOGGER,
listening_address=adapter,
listening_address=str(adapter_ip),
broadcast_address=CONF_MULTICAST_ADDRESS_DEFAULT,
broadcast_port=CONF_TARGET_PORT_DEFAULT,
listening_port=CONF_LISTENING_PORT_DEFAULT,
@@ -41,9 +40,10 @@ async def _async_has_devices(hass: HomeAssistant) -> bool:
)
try:
_LOGGER.debug("Starting discovery with IP %s", adapter_ip)
await controller.start()
except OSError as ex:
_LOGGER.error("Start failed, errno: %d", ex.errno)
_LOGGER.error("Start failed on IP %s, errno: %d", adapter_ip, ex.errno)
return False
try:
@@ -51,16 +51,34 @@ async def _async_has_devices(hass: HomeAssistant) -> bool:
while not controller.devices:
await asyncio.sleep(delay=1)
except TimeoutError:
_LOGGER.debug("No devices found")
_LOGGER.debug("No devices found with IP %s", adapter_ip)
devices_count = len(controller.devices)
cleanup_complete: asyncio.Event = controller.cleanup()
cleanup_complete_events: list[asyncio.Event] = []
with suppress(TimeoutError):
await asyncio.wait_for(cleanup_complete.wait(), 1)
await asyncio.gather(
*[
asyncio.wait_for(cleanup_complete_event.wait(), 1)
for cleanup_complete_event in cleanup_complete_events
]
)
return devices_count > 0
async def _async_has_devices(hass: HomeAssistant) -> bool:
"""Return if there are devices that can be discovered."""
# Get source IPs for all enabled adapters
source_ips = await network.async_get_enabled_source_ips(hass)
_LOGGER.debug("Enabled source IPs: %s", source_ips)
# Run discovery on every IPv4 address and gather results
results = await asyncio.gather(*[_async_discover(hass, ip) for ip in source_ips])
return any(results)
config_entry_flow.register_discovery_flow(
DOMAIN, "Govee light local", _async_has_devices
)

View File

@@ -2,6 +2,7 @@
import asyncio
from collections.abc import Callable
from ipaddress import IPv4Address, IPv6Address
import logging
from govee_local_api import GoveeController, GoveeDevice
@@ -11,7 +12,6 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import (
CONF_DISCOVERY_INTERVAL_DEFAULT,
CONF_LISTENING_PORT_DEFAULT,
CONF_MULTICAST_ADDRESS_DEFAULT,
CONF_TARGET_PORT_DEFAULT,
@@ -26,10 +26,11 @@ type GoveeLocalConfigEntry = ConfigEntry[GoveeLocalApiCoordinator]
class GoveeLocalApiCoordinator(DataUpdateCoordinator[list[GoveeDevice]]):
"""Govee light local coordinator."""
config_entry: GoveeLocalConfigEntry
def __init__(
self, hass: HomeAssistant, config_entry: GoveeLocalConfigEntry
self,
hass: HomeAssistant,
config_entry: GoveeLocalConfigEntry,
source_ips: list[IPv4Address | IPv6Address],
) -> None:
"""Initialize my coordinator."""
super().__init__(
@@ -40,32 +41,40 @@ class GoveeLocalApiCoordinator(DataUpdateCoordinator[list[GoveeDevice]]):
update_interval=SCAN_INTERVAL,
)
self._controller = GoveeController(
loop=hass.loop,
logger=_LOGGER,
broadcast_address=CONF_MULTICAST_ADDRESS_DEFAULT,
broadcast_port=CONF_TARGET_PORT_DEFAULT,
listening_port=CONF_LISTENING_PORT_DEFAULT,
discovery_enabled=True,
discovery_interval=CONF_DISCOVERY_INTERVAL_DEFAULT,
discovered_callback=None,
update_enabled=False,
)
self._controllers: list[GoveeController] = [
GoveeController(
loop=hass.loop,
logger=_LOGGER,
listening_address=str(source_ip),
broadcast_address=CONF_MULTICAST_ADDRESS_DEFAULT,
broadcast_port=CONF_TARGET_PORT_DEFAULT,
listening_port=CONF_LISTENING_PORT_DEFAULT,
discovery_enabled=True,
discovery_interval=1,
update_enabled=False,
)
for source_ip in source_ips
]
async def start(self) -> None:
"""Start the Govee coordinator."""
await self._controller.start()
self._controller.send_update_message()
for controller in self._controllers:
await controller.start()
controller.send_update_message()
async def set_discovery_callback(
self, callback: Callable[[GoveeDevice, bool], bool]
) -> None:
"""Set discovery callback for automatic Govee light discovery."""
self._controller.set_device_discovered_callback(callback)
def cleanup(self) -> asyncio.Event:
"""Stop and cleanup the cooridinator."""
return self._controller.cleanup()
for controller in self._controllers:
controller.set_device_discovered_callback(callback)
def cleanup(self) -> list[asyncio.Event]:
"""Stop and cleanup the coordinator."""
return [controller.cleanup() for controller in self._controllers]
async def turn_on(self, device: GoveeDevice) -> None:
"""Turn on the light."""
@@ -96,8 +105,14 @@ class GoveeLocalApiCoordinator(DataUpdateCoordinator[list[GoveeDevice]]):
@property
def devices(self) -> list[GoveeDevice]:
"""Return a list of discovered Govee devices."""
return self._controller.devices
devices: list[GoveeDevice] = []
for controller in self._controllers:
devices = devices + controller.devices
return devices
async def _async_update_data(self) -> list[GoveeDevice]:
self._controller.send_update_message()
return self._controller.devices
for controller in self._controllers:
controller.send_update_message()
return self.devices