From bce7e9ba5e6604da250ad2cd53defe58fca04a5f Mon Sep 17 00:00:00 2001 From: cdnninja Date: Fri, 10 Jan 2025 04:30:29 -0700 Subject: [PATCH] Simplify vesync init loading (#135052) --- homeassistant/components/vesync/__init__.py | 105 +++----------------- homeassistant/components/vesync/common.py | 39 ++------ homeassistant/components/vesync/const.py | 6 +- homeassistant/components/vesync/fan.py | 6 +- homeassistant/components/vesync/light.py | 6 +- homeassistant/components/vesync/sensor.py | 6 +- homeassistant/components/vesync/switch.py | 6 +- tests/components/vesync/test_init.py | 65 ++++++++---- 8 files changed, 78 insertions(+), 161 deletions(-) diff --git a/homeassistant/components/vesync/__init__.py b/homeassistant/components/vesync/__init__.py index 8e8b7744988..c48363b046d 100644 --- a/homeassistant/components/vesync/__init__.py +++ b/homeassistant/components/vesync/__init__.py @@ -9,17 +9,14 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers.dispatcher import async_dispatcher_send -from .common import async_process_devices +from .common import async_generate_device_list from .const import ( DOMAIN, SERVICE_UPDATE_DEVS, VS_COORDINATOR, + VS_DEVICES, VS_DISCOVERY, - VS_FANS, - VS_LIGHTS, VS_MANAGER, - VS_SENSORS, - VS_SWITCHES, ) from .coordinator import VeSyncDataCoordinator @@ -43,10 +40,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b _LOGGER.error("Unable to login to the VeSync server") return False - device_dict = await async_process_devices(hass, manager) - - forward_setups = hass.config_entries.async_forward_entry_setups - hass.data[DOMAIN] = {} hass.data[DOMAIN][VS_MANAGER] = manager @@ -55,83 +48,25 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b # Store coordinator at domain level since only single integration instance is permitted. hass.data[DOMAIN][VS_COORDINATOR] = coordinator - switches = hass.data[DOMAIN][VS_SWITCHES] = [] - fans = hass.data[DOMAIN][VS_FANS] = [] - lights = hass.data[DOMAIN][VS_LIGHTS] = [] - sensors = hass.data[DOMAIN][VS_SENSORS] = [] - platforms = [] + hass.data[DOMAIN][VS_DEVICES] = await async_generate_device_list(hass, manager) - if device_dict[VS_SWITCHES]: - switches.extend(device_dict[VS_SWITCHES]) - platforms.append(Platform.SWITCH) - - if device_dict[VS_FANS]: - fans.extend(device_dict[VS_FANS]) - platforms.append(Platform.FAN) - - if device_dict[VS_LIGHTS]: - lights.extend(device_dict[VS_LIGHTS]) - platforms.append(Platform.LIGHT) - - if device_dict[VS_SENSORS]: - sensors.extend(device_dict[VS_SENSORS]) - platforms.append(Platform.SENSOR) - - await hass.config_entries.async_forward_entry_setups(config_entry, platforms) + await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) async def async_new_device_discovery(service: ServiceCall) -> None: """Discover if new devices should be added.""" manager = hass.data[DOMAIN][VS_MANAGER] - switches = hass.data[DOMAIN][VS_SWITCHES] - fans = hass.data[DOMAIN][VS_FANS] - lights = hass.data[DOMAIN][VS_LIGHTS] - sensors = hass.data[DOMAIN][VS_SENSORS] + devices = hass.data[DOMAIN][VS_DEVICES] - dev_dict = await async_process_devices(hass, manager) - switch_devs = dev_dict.get(VS_SWITCHES, []) - fan_devs = dev_dict.get(VS_FANS, []) - light_devs = dev_dict.get(VS_LIGHTS, []) - sensor_devs = dev_dict.get(VS_SENSORS, []) + new_devices = await async_generate_device_list(hass, manager) - switch_set = set(switch_devs) - new_switches = list(switch_set.difference(switches)) - if new_switches and switches: - switches.extend(new_switches) - async_dispatcher_send(hass, VS_DISCOVERY.format(VS_SWITCHES), new_switches) + device_set = set(new_devices) + new_devices = list(device_set.difference(devices)) + if new_devices and devices: + devices.extend(new_devices) + async_dispatcher_send(hass, VS_DISCOVERY.format(VS_DEVICES), new_devices) return - if new_switches and not switches: - switches.extend(new_switches) - hass.async_create_task(forward_setups(config_entry, [Platform.SWITCH])) - - fan_set = set(fan_devs) - new_fans = list(fan_set.difference(fans)) - if new_fans and fans: - fans.extend(new_fans) - async_dispatcher_send(hass, VS_DISCOVERY.format(VS_FANS), new_fans) - return - if new_fans and not fans: - fans.extend(new_fans) - hass.async_create_task(forward_setups(config_entry, [Platform.FAN])) - - light_set = set(light_devs) - new_lights = list(light_set.difference(lights)) - if new_lights and lights: - lights.extend(new_lights) - async_dispatcher_send(hass, VS_DISCOVERY.format(VS_LIGHTS), new_lights) - return - if new_lights and not lights: - lights.extend(new_lights) - hass.async_create_task(forward_setups(config_entry, [Platform.LIGHT])) - - sensor_set = set(sensor_devs) - new_sensors = list(sensor_set.difference(sensors)) - if new_sensors and sensors: - sensors.extend(new_sensors) - async_dispatcher_send(hass, VS_DISCOVERY.format(VS_SENSORS), new_sensors) - return - if new_sensors and not sensors: - sensors.extend(new_sensors) - hass.async_create_task(forward_setups(config_entry, [Platform.SENSOR])) + if new_devices and not devices: + devices.extend(new_devices) hass.services.async_register( DOMAIN, SERVICE_UPDATE_DEVS, async_new_device_discovery @@ -142,18 +77,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - in_use_platforms = [] - if hass.data[DOMAIN][VS_SWITCHES]: - in_use_platforms.append(Platform.SWITCH) - if hass.data[DOMAIN][VS_FANS]: - in_use_platforms.append(Platform.FAN) - if hass.data[DOMAIN][VS_LIGHTS]: - in_use_platforms.append(Platform.LIGHT) - if hass.data[DOMAIN][VS_SENSORS]: - in_use_platforms.append(Platform.SENSOR) - unload_ok = await hass.config_entries.async_unload_platforms( - entry, in_use_platforms - ) + + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: hass.data.pop(DOMAIN) diff --git a/homeassistant/components/vesync/common.py b/homeassistant/components/vesync/common.py index 5412b4f970c..ce4235d20f8 100644 --- a/homeassistant/components/vesync/common.py +++ b/homeassistant/components/vesync/common.py @@ -7,45 +7,20 @@ from pyvesync.vesyncbasedevice import VeSyncBaseDevice from homeassistant.core import HomeAssistant -from .const import VS_FANS, VS_LIGHTS, VS_SENSORS, VS_SWITCHES - _LOGGER = logging.getLogger(__name__) -async def async_process_devices( +async def async_generate_device_list( hass: HomeAssistant, manager: VeSync -) -> dict[str, list[VeSyncBaseDevice]]: +) -> list[VeSyncBaseDevice]: """Assign devices to proper component.""" - devices: dict[str, list[VeSyncBaseDevice]] = {} - devices[VS_SWITCHES] = [] - devices[VS_FANS] = [] - devices[VS_LIGHTS] = [] - devices[VS_SENSORS] = [] + devices: list[VeSyncBaseDevice] = [] await hass.async_add_executor_job(manager.update) - if manager.fans: - devices[VS_FANS].extend(manager.fans) - # Expose fan sensors separately - devices[VS_SENSORS].extend(manager.fans) - _LOGGER.debug("%d VeSync fans found", len(manager.fans)) - - if manager.bulbs: - devices[VS_LIGHTS].extend(manager.bulbs) - _LOGGER.debug("%d VeSync lights found", len(manager.bulbs)) - - if manager.outlets: - devices[VS_SWITCHES].extend(manager.outlets) - # Expose outlets' voltage, power & energy usage as separate sensors - devices[VS_SENSORS].extend(manager.outlets) - _LOGGER.debug("%d VeSync outlets found", len(manager.outlets)) - - if manager.switches: - for switch in manager.switches: - if not switch.is_dimmable(): - devices[VS_SWITCHES].append(switch) - else: - devices[VS_LIGHTS].append(switch) - _LOGGER.debug("%d VeSync switches found", len(manager.switches)) + devices.extend(manager.fans) + devices.extend(manager.bulbs) + devices.extend(manager.outlets) + devices.extend(manager.switches) return devices diff --git a/homeassistant/components/vesync/const.py b/homeassistant/components/vesync/const.py index 2a8c5722340..6a27e7330ac 100644 --- a/homeassistant/components/vesync/const.py +++ b/homeassistant/components/vesync/const.py @@ -17,11 +17,7 @@ total would be 2880. Using 30 seconds interval gives 8640 for 3 devices which exceeds the quota of 7700. """ - -VS_SWITCHES = "switches" -VS_FANS = "fans" -VS_LIGHTS = "lights" -VS_SENSORS = "sensors" +VS_DEVICES = "devices" VS_COORDINATOR = "coordinator" VS_MANAGER = "manager" diff --git a/homeassistant/components/vesync/fan.py b/homeassistant/components/vesync/fan.py index c6d61feebef..9ef0940e8d0 100644 --- a/homeassistant/components/vesync/fan.py +++ b/homeassistant/components/vesync/fan.py @@ -24,8 +24,8 @@ from .const import ( DOMAIN, SKU_TO_BASE_DEVICE, VS_COORDINATOR, + VS_DEVICES, VS_DISCOVERY, - VS_FANS, ) from .coordinator import VeSyncDataCoordinator from .entity import VeSyncBaseEntity @@ -74,10 +74,10 @@ async def async_setup_entry( _setup_entities(devices, async_add_entities, coordinator) config_entry.async_on_unload( - async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_FANS), discover) + async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_DEVICES), discover) ) - _setup_entities(hass.data[DOMAIN][VS_FANS], async_add_entities, coordinator) + _setup_entities(hass.data[DOMAIN][VS_DEVICES], async_add_entities, coordinator) @callback diff --git a/homeassistant/components/vesync/light.py b/homeassistant/components/vesync/light.py index 84324e0af6e..f58b9180e12 100644 --- a/homeassistant/components/vesync/light.py +++ b/homeassistant/components/vesync/light.py @@ -17,7 +17,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import color as color_util -from .const import DEV_TYPE_TO_HA, DOMAIN, VS_COORDINATOR, VS_DISCOVERY, VS_LIGHTS +from .const import DEV_TYPE_TO_HA, DOMAIN, VS_COORDINATOR, VS_DEVICES, VS_DISCOVERY from .coordinator import VeSyncDataCoordinator from .entity import VeSyncBaseEntity @@ -41,10 +41,10 @@ async def async_setup_entry( _setup_entities(devices, async_add_entities, coordinator) config_entry.async_on_unload( - async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_LIGHTS), discover) + async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_DEVICES), discover) ) - _setup_entities(hass.data[DOMAIN][VS_LIGHTS], async_add_entities, coordinator) + _setup_entities(hass.data[DOMAIN][VS_DEVICES], async_add_entities, coordinator) @callback diff --git a/homeassistant/components/vesync/sensor.py b/homeassistant/components/vesync/sensor.py index f283e3a3c0a..59c45d435d4 100644 --- a/homeassistant/components/vesync/sensor.py +++ b/homeassistant/components/vesync/sensor.py @@ -36,8 +36,8 @@ from .const import ( DOMAIN, SKU_TO_BASE_DEVICE, VS_COORDINATOR, + VS_DEVICES, VS_DISCOVERY, - VS_SENSORS, ) from .coordinator import VeSyncDataCoordinator from .entity import VeSyncBaseEntity @@ -204,10 +204,10 @@ async def async_setup_entry( _setup_entities(devices, async_add_entities, coordinator) config_entry.async_on_unload( - async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_SENSORS), discover) + async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_DEVICES), discover) ) - _setup_entities(hass.data[DOMAIN][VS_SENSORS], async_add_entities, coordinator) + _setup_entities(hass.data[DOMAIN][VS_DEVICES], async_add_entities, coordinator) @callback diff --git a/homeassistant/components/vesync/switch.py b/homeassistant/components/vesync/switch.py index 0b69ca3d44a..a3c628c596d 100644 --- a/homeassistant/components/vesync/switch.py +++ b/homeassistant/components/vesync/switch.py @@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DEV_TYPE_TO_HA, DOMAIN, VS_COORDINATOR, VS_DISCOVERY, VS_SWITCHES +from .const import DEV_TYPE_TO_HA, DOMAIN, VS_COORDINATOR, VS_DEVICES, VS_DISCOVERY from .coordinator import VeSyncDataCoordinator from .entity import VeSyncBaseEntity @@ -33,10 +33,10 @@ async def async_setup_entry( _setup_entities(devices, async_add_entities, coordinator) config_entry.async_on_unload( - async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_SWITCHES), discover) + async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_DEVICES), discover) ) - _setup_entities(hass.data[DOMAIN][VS_SWITCHES], async_add_entities, coordinator) + _setup_entities(hass.data[DOMAIN][VS_DEVICES], async_add_entities, coordinator) @callback diff --git a/tests/components/vesync/test_init.py b/tests/components/vesync/test_init.py index a089a270c94..dc0541b3c21 100644 --- a/tests/components/vesync/test_init.py +++ b/tests/components/vesync/test_init.py @@ -5,16 +5,9 @@ from unittest.mock import Mock, patch import pytest from pyvesync import VeSync -from homeassistant.components.vesync import async_setup_entry -from homeassistant.components.vesync.const import ( - DOMAIN, - VS_FANS, - VS_LIGHTS, - VS_MANAGER, - VS_SENSORS, - VS_SWITCHES, -) -from homeassistant.config_entries import ConfigEntry +from homeassistant.components.vesync import SERVICE_UPDATE_DEVS, async_setup_entry +from homeassistant.components.vesync.const import DOMAIN, VS_DEVICES, VS_MANAGER +from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -30,7 +23,9 @@ async def test_async_setup_entry__not_login( with ( patch.object(hass.config_entries, "async_forward_entry_setups") as setups_mock, - patch("homeassistant.components.vesync.async_process_devices") as process_mock, + patch( + "homeassistant.components.vesync.async_generate_device_list" + ) as process_mock, ): assert not await async_setup_entry(hass, config_entry) await hass.async_block_till_done() @@ -52,20 +47,22 @@ async def test_async_setup_entry__no_devices( await hass.async_block_till_done() assert setups_mock.call_count == 1 assert setups_mock.call_args.args[0] == config_entry - assert setups_mock.call_args.args[1] == [] + assert setups_mock.call_args.args[1] == [ + Platform.FAN, + Platform.LIGHT, + Platform.SENSOR, + Platform.SWITCH, + ] assert manager.login.call_count == 1 assert hass.data[DOMAIN][VS_MANAGER] == manager - assert not hass.data[DOMAIN][VS_SWITCHES] - assert not hass.data[DOMAIN][VS_FANS] - assert not hass.data[DOMAIN][VS_LIGHTS] - assert not hass.data[DOMAIN][VS_SENSORS] + assert not hass.data[DOMAIN][VS_DEVICES] async def test_async_setup_entry__loads_fans( hass: HomeAssistant, config_entry: ConfigEntry, manager: VeSync, fan ) -> None: - """Test setup connects to vesync and loads fan platform.""" + """Test setup connects to vesync and loads fan.""" fans = [fan] manager.fans = fans manager._dev_list = { @@ -78,10 +75,34 @@ async def test_async_setup_entry__loads_fans( await hass.async_block_till_done() assert setups_mock.call_count == 1 assert setups_mock.call_args.args[0] == config_entry - assert setups_mock.call_args.args[1] == [Platform.FAN, Platform.SENSOR] + assert setups_mock.call_args.args[1] == [ + Platform.FAN, + Platform.LIGHT, + Platform.SENSOR, + Platform.SWITCH, + ] assert manager.login.call_count == 1 assert hass.data[DOMAIN][VS_MANAGER] == manager - assert not hass.data[DOMAIN][VS_SWITCHES] - assert hass.data[DOMAIN][VS_FANS] == [fan] - assert not hass.data[DOMAIN][VS_LIGHTS] - assert hass.data[DOMAIN][VS_SENSORS] == [fan] + assert hass.data[DOMAIN][VS_DEVICES] == [fan] + + +async def test_async_new_device_discovery__loads_fans( + hass: HomeAssistant, config_entry: ConfigEntry, manager: VeSync, fan +) -> None: + """Test setup connects to vesync and loads fan as an update call.""" + + assert await hass.config_entries.async_setup(config_entry.entry_id) + # Assert platforms loaded + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.LOADED + assert not hass.data[DOMAIN][VS_DEVICES] + fans = [fan] + manager.fans = fans + manager._dev_list = { + "fans": fans, + } + await hass.services.async_call(DOMAIN, SERVICE_UPDATE_DEVS, {}, blocking=True) + + assert manager.login.call_count == 1 + assert hass.data[DOMAIN][VS_MANAGER] == manager + assert hass.data[DOMAIN][VS_DEVICES] == [fan]