Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion homeassistant/components/enphase_envoy/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ async def async_get_config_entry_diagnostics(
device_dict.pop("_cache", None)
# This can be removed when suggested_area is removed from DeviceEntry
device_dict.pop("_suggested_area")
device_dict.pop("is_new", None)
device_entities.append({"device": device_dict, "entities": entities})

# remove envoy serial
Expand Down
6 changes: 0 additions & 6 deletions homeassistant/components/google_cloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,9 @@
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry."""
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_update_options))
return True


async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
4 changes: 2 additions & 2 deletions homeassistant/components/google_cloud/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
OptionsFlowWithReload,
)
from homeassistant.core import callback
from homeassistant.helpers.selector import (
Expand Down Expand Up @@ -138,7 +138,7 @@ def async_get_options_flow(
return GoogleCloudOptionsFlowHandler()


class GoogleCloudOptionsFlowHandler(OptionsFlow):
class GoogleCloudOptionsFlowHandler(OptionsFlowWithReload):
"""Google Cloud options flow."""

async def async_step_init(
Expand Down
19 changes: 16 additions & 3 deletions homeassistant/components/govee_light_local/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

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
Expand All @@ -23,12 +24,24 @@

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)

Expand Down
38 changes: 28 additions & 10 deletions homeassistant/components/govee_light_local/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import asyncio
from contextlib import suppress
from ipaddress import IPv4Address, IPv6Address
import logging

from govee_local_api import GoveeController
Expand All @@ -23,15 +24,13 @@
_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,
Expand All @@ -41,26 +40,45 @@ 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:
async with asyncio.timeout(delay=DISCOVERY_TIMEOUT):
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
)
63 changes: 39 additions & 24 deletions homeassistant/components/govee_light_local/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -11,7 +12,6 @@
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,
Expand All @@ -26,10 +26,11 @@
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__(
Expand All @@ -40,32 +41,40 @@ def __init__(
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."""
Expand Down Expand Up @@ -96,8 +105,14 @@ async def set_scene(self, device: GoveeController, scene: str) -> None:
@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
2 changes: 1 addition & 1 deletion homeassistant/components/meteo_france/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"Bancs de Brouillard",
"Brouillard dense",
],
ATTR_CONDITION_HAIL: ["Risque de grêle", "Risque de grèle"],
ATTR_CONDITION_HAIL: ["Risque de grêle", "Risque de grèle", "Averses de grèle"],
ATTR_CONDITION_LIGHTNING: ["Risque d'orages", "Orages", "Orage avec grêle"],
ATTR_CONDITION_LIGHTNING_RAINY: [
"Pluie orageuses",
Expand Down
Loading
Loading