Simplify vesync init loading (#135052)

This commit is contained in:
cdnninja 2025-01-10 04:30:29 -07:00 committed by GitHub
parent 475a2fb828
commit bce7e9ba5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 78 additions and 161 deletions

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]