Migrate elkm1 to use a dataclass for integration data (#99830)

* Migrate elkm1 to use a dataclass for integration data

* fix unsaved

* slotted

* missing coveragerc

* Revert "missing coveragerc"

This reverts commit 3397b40309033276d20fef59098b0a1b5b681a30.
This commit is contained in:
J. Nick Koston 2023-09-08 12:07:09 -05:00 committed by GitHub
parent 3d403c9b60
commit d624bbbc0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 77 additions and 47 deletions

View File

@ -2,11 +2,12 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from collections.abc import Iterable
from enum import Enum from enum import Enum
import logging import logging
import re import re
from types import MappingProxyType from types import MappingProxyType
from typing import Any, cast from typing import Any
from elkm1_lib.elements import Element from elkm1_lib.elements import Element
from elkm1_lib.elk import Elk from elkm1_lib.elk import Elk
@ -65,6 +66,7 @@ from .discovery import (
async_trigger_discovery, async_trigger_discovery,
async_update_entry_from_discovery, async_update_entry_from_discovery,
) )
from .models import ELKM1Data
SYNC_TIMEOUT = 120 SYNC_TIMEOUT = 120
@ -303,14 +305,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
else: else:
temperature_unit = UnitOfTemperature.FAHRENHEIT temperature_unit = UnitOfTemperature.FAHRENHEIT
config["temperature_unit"] = temperature_unit config["temperature_unit"] = temperature_unit
hass.data[DOMAIN][entry.entry_id] = { prefix: str = conf[CONF_PREFIX]
"elk": elk, auto_configure: bool = conf[CONF_AUTO_CONFIGURE]
"prefix": conf[CONF_PREFIX], hass.data[DOMAIN][entry.entry_id] = ELKM1Data(
"mac": entry.unique_id, elk=elk,
"auto_configure": conf[CONF_AUTO_CONFIGURE], prefix=prefix,
"config": config, mac=entry.unique_id,
"keypads": {}, auto_configure=auto_configure,
} config=config,
keypads={},
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@ -326,21 +330,23 @@ def _included(ranges: list[tuple[int, int]], set_to: bool, values: list[bool]) -
def _find_elk_by_prefix(hass: HomeAssistant, prefix: str) -> Elk | None: def _find_elk_by_prefix(hass: HomeAssistant, prefix: str) -> Elk | None:
"""Search all config entries for a given prefix.""" """Search all config entries for a given prefix."""
for entry_id in hass.data[DOMAIN]: all_elk: dict[str, ELKM1Data] = hass.data[DOMAIN]
if hass.data[DOMAIN][entry_id]["prefix"] == prefix: for elk_data in all_elk.values():
return cast(Elk, hass.data[DOMAIN][entry_id]["elk"]) if elk_data.prefix == prefix:
return elk_data.elk
return None return None
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
all_elk: dict[str, ELKM1Data] = hass.data[DOMAIN]
# disconnect cleanly # disconnect cleanly
hass.data[DOMAIN][entry.entry_id]["elk"].disconnect() all_elk[entry.entry_id].elk.disconnect()
if unload_ok: if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id) all_elk.pop(entry.entry_id)
return unload_ok return unload_ok
@ -421,19 +427,19 @@ def _create_elk_services(hass: HomeAssistant) -> None:
def create_elk_entities( def create_elk_entities(
elk_data: dict[str, Any], elk_data: ELKM1Data,
elk_elements: list[Element], elk_elements: Iterable[Element],
element_type: str, element_type: str,
class_: Any, class_: Any,
entities: list[ElkEntity], entities: list[ElkEntity],
) -> list[ElkEntity] | None: ) -> list[ElkEntity] | None:
"""Create the ElkM1 devices of a particular class.""" """Create the ElkM1 devices of a particular class."""
auto_configure = elk_data["auto_configure"] auto_configure = elk_data.auto_configure
if not auto_configure and not elk_data["config"][element_type]["enabled"]: if not auto_configure and not elk_data.config[element_type]["enabled"]:
return None return None
elk = elk_data["elk"] elk = elk_data.elk
_LOGGER.debug("Creating elk entities for %s", elk) _LOGGER.debug("Creating elk entities for %s", elk)
for element in elk_elements: for element in elk_elements:
@ -441,7 +447,7 @@ def create_elk_entities(
if not element.configured: if not element.configured:
continue continue
# Only check the included list if auto configure is not # Only check the included list if auto configure is not
elif not elk_data["config"][element_type]["included"][element.index]: elif not elk_data.config[element_type]["included"][element.index]:
continue continue
entities.append(class_(element, elk, elk_data)) entities.append(class_(element, elk, elk_data))
@ -454,13 +460,13 @@ class ElkEntity(Entity):
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_should_poll = False _attr_should_poll = False
def __init__(self, element: Element, elk: Elk, elk_data: dict[str, Any]) -> None: def __init__(self, element: Element, elk: Elk, elk_data: ELKM1Data) -> None:
"""Initialize the base of all Elk devices.""" """Initialize the base of all Elk devices."""
self._elk = elk self._elk = elk
self._element = element self._element = element
self._mac = elk_data["mac"] self._mac = elk_data.mac
self._prefix = elk_data["prefix"] self._prefix = elk_data.prefix
self._temperature_unit: str = elk_data["config"]["temperature_unit"] self._temperature_unit: str = elk_data.config["temperature_unit"]
# unique_id starts with elkm1_ iff there is no prefix # unique_id starts with elkm1_ iff there is no prefix
# it starts with elkm1m_{prefix} iff there is a prefix # it starts with elkm1m_{prefix} iff there is a prefix
# this is to avoid a conflict between # this is to avoid a conflict between
@ -496,9 +502,7 @@ class ElkEntity(Entity):
def initial_attrs(self) -> dict[str, Any]: def initial_attrs(self) -> dict[str, Any]:
"""Return the underlying element's attributes as a dict.""" """Return the underlying element's attributes as a dict."""
attrs = {} return {"index": self._element.index + 1}
attrs["index"] = self._element.index + 1
return attrs
def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None: def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None:
pass pass

View File

@ -40,6 +40,7 @@ from .const import (
DOMAIN, DOMAIN,
ELK_USER_CODE_SERVICE_SCHEMA, ELK_USER_CODE_SERVICE_SCHEMA,
) )
from .models import ELKM1Data
DISPLAY_MESSAGE_SERVICE_SCHEMA = { DISPLAY_MESSAGE_SERVICE_SCHEMA = {
vol.Optional("clear", default=2): vol.All(vol.Coerce(int), vol.In([0, 1, 2])), vol.Optional("clear", default=2): vol.All(vol.Coerce(int), vol.In([0, 1, 2])),
@ -65,8 +66,9 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the ElkM1 alarm platform.""" """Set up the ElkM1 alarm platform."""
elk_data = hass.data[DOMAIN][config_entry.entry_id]
elk = elk_data["elk"] elk_data: ELKM1Data = hass.data[DOMAIN][config_entry.entry_id]
elk = elk_data.elk
entities: list[ElkEntity] = [] entities: list[ElkEntity] = []
create_elk_entities(elk_data, elk.areas, "area", ElkArea, entities) create_elk_entities(elk_data, elk.areas, "area", ElkArea, entities)
async_add_entities(entities) async_add_entities(entities)
@ -115,7 +117,7 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity):
) )
_element: Area _element: Area
def __init__(self, element: Element, elk: Elk, elk_data: dict[str, Any]) -> None: def __init__(self, element: Element, elk: Elk, elk_data: ELKM1Data) -> None:
"""Initialize Area as Alarm Control Panel.""" """Initialize Area as Alarm Control Panel."""
super().__init__(element, elk, elk_data) super().__init__(element, elk, elk_data)
self._elk = elk self._elk = elk

View File

@ -14,6 +14,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ElkAttachedEntity, ElkEntity from . import ElkAttachedEntity, ElkEntity
from .const import DOMAIN from .const import DOMAIN
from .models import ELKM1Data
async def async_setup_entry( async def async_setup_entry(
@ -22,21 +23,20 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Create the Elk-M1 sensor platform.""" """Create the Elk-M1 sensor platform."""
elk_data: ELKM1Data = hass.data[DOMAIN][config_entry.entry_id]
elk_data = hass.data[DOMAIN][config_entry.entry_id] elk = elk_data.elk
auto_configure = elk_data["auto_configure"] auto_configure = elk_data.auto_configure
elk = elk_data["elk"]
entities: list[ElkEntity] = [] entities: list[ElkEntity] = []
for element in elk.zones: for element in elk.zones:
# Don't create binary sensors for zones that are analog # Don't create binary sensors for zones that are analog
if element.definition in {ZoneType.TEMPERATURE, ZoneType.ANALOG_ZONE}: if element.definition in {ZoneType.TEMPERATURE, ZoneType.ANALOG_ZONE}: # type: ignore[attr-defined]
continue continue
if auto_configure: if auto_configure:
if not element.configured: if not element.configured:
continue continue
elif not elk_data["config"]["zone"]["included"][element.index]: elif not elk_data.config["zone"]["included"][element.index]:
continue continue
entities.append(ElkBinarySensor(element, elk, elk_data)) entities.append(ElkBinarySensor(element, elk, elk_data))

View File

@ -23,6 +23,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ElkEntity, create_elk_entities from . import ElkEntity, create_elk_entities
from .const import DOMAIN from .const import DOMAIN
from .models import ELKM1Data
SUPPORT_HVAC = [ SUPPORT_HVAC = [
HVACMode.OFF, HVACMode.OFF,
@ -61,9 +62,9 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Create the Elk-M1 thermostat platform.""" """Create the Elk-M1 thermostat platform."""
elk_data = hass.data[DOMAIN][config_entry.entry_id] elk_data: ELKM1Data = hass.data[DOMAIN][config_entry.entry_id]
elk = elk_data.elk
entities: list[ElkEntity] = [] entities: list[ElkEntity] = []
elk = elk_data["elk"]
create_elk_entities( create_elk_entities(
elk_data, elk.thermostats, "thermostat", ElkThermostat, entities elk_data, elk.thermostats, "thermostat", ElkThermostat, entities
) )

View File

@ -14,6 +14,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ElkEntity, create_elk_entities from . import ElkEntity, create_elk_entities
from .const import DOMAIN from .const import DOMAIN
from .models import ELKM1Data
async def async_setup_entry( async def async_setup_entry(
@ -22,9 +23,9 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the Elk light platform.""" """Set up the Elk light platform."""
elk_data = hass.data[DOMAIN][config_entry.entry_id] elk_data: ELKM1Data = hass.data[DOMAIN][config_entry.entry_id]
elk = elk_data.elk
entities: list[ElkEntity] = [] entities: list[ElkEntity] = []
elk = elk_data["elk"]
create_elk_entities(elk_data, elk.lights, "plc", ElkLight, entities) create_elk_entities(elk_data, elk.lights, "plc", ElkLight, entities)
async_add_entities(entities) async_add_entities(entities)
@ -36,7 +37,7 @@ class ElkLight(ElkEntity, LightEntity):
_attr_supported_color_modes = {ColorMode.BRIGHTNESS} _attr_supported_color_modes = {ColorMode.BRIGHTNESS}
_element: Light _element: Light
def __init__(self, element: Element, elk: Elk, elk_data: dict[str, Any]) -> None: def __init__(self, element: Element, elk: Elk, elk_data: ELKM1Data) -> None:
"""Initialize the Elk light.""" """Initialize the Elk light."""
super().__init__(element, elk, elk_data) super().__init__(element, elk, elk_data)
self._brightness = self._element.status self._brightness = self._element.status

View File

@ -0,0 +1,19 @@
"""The elkm1 integration models."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from elkm1_lib import Elk
@dataclass(slots=True)
class ELKM1Data:
"""Data for the elkm1 integration."""
elk: Elk
prefix: str
mac: str | None
auto_configure: bool
config: dict[str, Any]
keypads: dict[str, Any]

View File

@ -12,6 +12,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ElkAttachedEntity, ElkEntity, create_elk_entities from . import ElkAttachedEntity, ElkEntity, create_elk_entities
from .const import DOMAIN from .const import DOMAIN
from .models import ELKM1Data
async def async_setup_entry( async def async_setup_entry(
@ -20,9 +21,9 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Create the Elk-M1 scene platform.""" """Create the Elk-M1 scene platform."""
elk_data = hass.data[DOMAIN][config_entry.entry_id] elk_data: ELKM1Data = hass.data[DOMAIN][config_entry.entry_id]
elk = elk_data.elk
entities: list[ElkEntity] = [] entities: list[ElkEntity] = []
elk = elk_data["elk"]
create_elk_entities(elk_data, elk.tasks, "task", ElkTask, entities) create_elk_entities(elk_data, elk.tasks, "task", ElkTask, entities)
async_add_entities(entities) async_add_entities(entities)

View File

@ -23,6 +23,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ElkAttachedEntity, ElkEntity, create_elk_entities from . import ElkAttachedEntity, ElkEntity, create_elk_entities
from .const import ATTR_VALUE, DOMAIN, ELK_USER_CODE_SERVICE_SCHEMA from .const import ATTR_VALUE, DOMAIN, ELK_USER_CODE_SERVICE_SCHEMA
from .models import ELKM1Data
SERVICE_SENSOR_COUNTER_REFRESH = "sensor_counter_refresh" SERVICE_SENSOR_COUNTER_REFRESH = "sensor_counter_refresh"
SERVICE_SENSOR_COUNTER_SET = "sensor_counter_set" SERVICE_SENSOR_COUNTER_SET = "sensor_counter_set"
@ -41,9 +42,9 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Create the Elk-M1 sensor platform.""" """Create the Elk-M1 sensor platform."""
elk_data = hass.data[DOMAIN][config_entry.entry_id] elk_data: ELKM1Data = hass.data[DOMAIN][config_entry.entry_id]
elk = elk_data.elk
entities: list[ElkEntity] = [] entities: list[ElkEntity] = []
elk = elk_data["elk"]
create_elk_entities(elk_data, elk.counters, "counter", ElkCounter, entities) create_elk_entities(elk_data, elk.counters, "counter", ElkCounter, entities)
create_elk_entities(elk_data, elk.keypads, "keypad", ElkKeypad, entities) create_elk_entities(elk_data, elk.keypads, "keypad", ElkKeypad, entities)
create_elk_entities(elk_data, [elk.panel], "panel", ElkPanel, entities) create_elk_entities(elk_data, [elk.panel], "panel", ElkPanel, entities)

View File

@ -12,6 +12,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ElkAttachedEntity, ElkEntity, create_elk_entities from . import ElkAttachedEntity, ElkEntity, create_elk_entities
from .const import DOMAIN from .const import DOMAIN
from .models import ELKM1Data
async def async_setup_entry( async def async_setup_entry(
@ -20,9 +21,9 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Create the Elk-M1 switch platform.""" """Create the Elk-M1 switch platform."""
elk_data = hass.data[DOMAIN][config_entry.entry_id] elk_data: ELKM1Data = hass.data[DOMAIN][config_entry.entry_id]
elk = elk_data.elk
entities: list[ElkEntity] = [] entities: list[ElkEntity] = []
elk = elk_data["elk"]
create_elk_entities(elk_data, elk.outputs, "output", ElkOutput, entities) create_elk_entities(elk_data, elk.outputs, "output", ElkOutput, entities)
async_add_entities(entities) async_add_entities(entities)