Skip to content

Commit

Permalink
Use DataUpdateCoordinator for all entities
Browse files Browse the repository at this point in the history
  • Loading branch information
PimDoos committed Feb 22, 2025
1 parent c95a053 commit a7a578b
Show file tree
Hide file tree
Showing 13 changed files with 305 additions and 327 deletions.
49 changes: 17 additions & 32 deletions custom_components/sessy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,17 @@
from sessypy.devices import get_sessy_device
from sessypy.util import SessyLoginException, SessyConnectionException, SessyNotSupportedException

from .const import DOMAIN, SERIAL_NUMBER, SESSY_DEVICE, SESSY_DEVICE_INFO
from .util import clear_cache_command, generate_device_info, setup_cache, setup_cache_commands
from .coordinator import setup_coordinators, update_coordinator_options
from .models import SessyConfigEntry, SessyRuntimeData
from .util import generate_device_info

_LOGGER = logging.getLogger(__name__)

PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR, Platform.SELECT, Platform.NUMBER, Platform.SWITCH, Platform.TIME, Platform.UPDATE]


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, config_entry: SessyConfigEntry) -> bool:
"""Set up Sessy from a config entry."""

hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config_entry.entry_id] = {}

hass.data[DOMAIN][config_entry.entry_id][SERIAL_NUMBER] = config_entry.data.get(CONF_USERNAME).upper()

# Prevent duplicate entries in older setups
if not config_entry.unique_id:
config_entry.unique_id = hass.data[DOMAIN][config_entry.entry_id][SERIAL_NUMBER]


host = config_entry.data.get(CONF_HOST)

_LOGGER.debug(f"Connecting to Sessy device at {host}")
Expand All @@ -42,7 +33,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
username = config_entry.data.get(CONF_USERNAME),
password = config_entry.data.get(CONF_PASSWORD),
)


# Prevent duplicate entries in older setups
if not config_entry.unique_id:
config_entry.unique_id = device.serial_number

except SessyLoginException as e:
raise ConfigEntryAuthFailed(f"Failed to connect to Sessy device at {host}: Authentication failed") from e
except SessyNotSupportedException as e:
Expand All @@ -55,22 +50,17 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
else:
_LOGGER.info(f"Connection to {device.__class__} at {device.host} successful")

hass.data[DOMAIN][config_entry.entry_id][SESSY_DEVICE] = device

# Setup caching
await setup_cache(hass, config_entry)
await setup_cache_commands(hass, config_entry, device)
config_entry.runtime_data = SessyRuntimeData(device = device)

# Update cache command on options flow update
async def update_cache_commands(hass, config_entry):
await setup_cache_commands(hass, config_entry, device, setup=False)
# Generate Device Info
config_entry.runtime_data.device_info = await generate_device_info(hass, config_entry, device)
config_entry.runtime_data.coordinators = await setup_coordinators(hass, config_entry, device)

config_entry.add_update_listener(
listener=update_cache_commands
listener=update_coordinator_options
)

# Generate Device Info
hass.data[DOMAIN][config_entry.entry_id][SESSY_DEVICE_INFO] = await generate_device_info(hass, config_entry, device)


await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)

Expand All @@ -79,10 +69,5 @@ async def update_cache_commands(hass, config_entry):

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
await clear_cache_command(hass, entry)
await hass.data[DOMAIN][entry.entry_id][SESSY_DEVICE].close()
if entry.entry_id in hass.data[DOMAIN]:
hass.data[DOMAIN].pop(entry.entry_id)

unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
return unload_ok
27 changes: 13 additions & 14 deletions custom_components/sessy/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
"""BinarySensor to read data from Sessy"""
from __future__ import annotations

from homeassistant.config_entries import ConfigEntry
from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorDeviceClass
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory

from sessypy.const import SessyApiCommand, SessySystemState
from sessypy.devices import SessyBattery
from sessypy.devices import SessyBattery, SessyDevice

from .const import DOMAIN, SESSY_DEVICE
from .util import (enum_to_options_list, get_cache_command, status_string_system_state)
from .sessyentity import SessyEntity
from .coordinator import SessyCoordinator, SessyCoordinatorEntity
from .models import SessyConfigEntry

import logging
_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities):
async def async_setup_entry(hass: HomeAssistant, config_entry: SessyConfigEntry, async_add_entities):
"""Set up the Sessy binary_sensors"""

device = hass.data[DOMAIN][config_entry.entry_id][SESSY_DEVICE]
device: SessyDevice = config_entry.runtime_data.device
coordinators = config_entry.runtime_data.coordinators
binary_sensors = []

if isinstance(device, SessyBattery):
# Power Status
try:
power_status: dict = get_cache_command(hass, config_entry, SessyApiCommand.POWER_STATUS)
power_status_coordinator: SessyCoordinator = coordinators[device.get_power_status]
power_status: dict = power_status_coordinator.raw_data

if "strategy_overridden" in power_status.get("sessy", dict()):
binary_sensors.append(
SessyBinarySensor(hass, config_entry, "Strategy Override",
SessyApiCommand.POWER_STATUS, "sessy.strategy_overridden",
power_status_coordinator, "sessy.strategy_overridden",
BinarySensorDeviceClass.RUNNING, entity_category=EntityCategory.DIAGNOSTIC)
)

Expand All @@ -39,15 +38,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn

async_add_entities(binary_sensors)

class SessyBinarySensor(SessyEntity, BinarySensorEntity):
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry, name: str,
cache_command: SessyApiCommand, cache_key,
class SessyBinarySensor(SessyCoordinatorEntity, BinarySensorEntity):
def __init__(self, hass: HomeAssistant, config_entry: SessyConfigEntry, name: str,
coordinator: SessyCoordinatorEntity, data_key,
device_class: BinarySensorDeviceClass = None,
transform_function: function = None,
entity_category: EntityCategory = None, enabled_default: bool = True):

super().__init__(hass=hass, config_entry=config_entry, name=name,
cache_command=cache_command, cache_key=cache_key,
coordinator=coordinator, data_key=data_key,
transform_function=transform_function)

self._attr_device_class = device_class
Expand Down
27 changes: 13 additions & 14 deletions custom_components/sessy/button.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,49 @@
"""Button entities to control Sessy"""
from __future__ import annotations
from enum import Enum

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.components.button import ButtonEntity, ButtonDeviceClass
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import EntityCategory

from sessypy.const import SessyApiCommand
from sessypy.devices import SessyDevice
from sessypy.util import SessyConnectionException, SessyNotSupportedException

from .coordinator import SessyCoordinator, SessyCoordinatorEntity
from .models import SessyConfigEntry

from .const import DOMAIN, SESSY_DEVICE
from .sessyentity import SessyEntity

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities):
async def async_setup_entry(hass: HomeAssistant, config_entry: SessyConfigEntry, async_add_entities):
"""Set up the Sessy buttons"""

device: SessyDevice = hass.data[DOMAIN][config_entry.entry_id][SESSY_DEVICE]
device: SessyDevice = config_entry.runtime_data.device
coordinators = config_entry.runtime_data.coordinators

buttons = []


buttons.append(
SessyButton(hass, config_entry, "Dongle Restart",
SessyApiCommand.SYSTEM_INFO, 'status', device.restart,
coordinators[device.get_system_info], 'status', device.restart,
device_class=ButtonDeviceClass.RESTART, entity_category=EntityCategory.DIAGNOSTIC
)
)
buttons.append(
SessyButton(hass, config_entry, "Check for updates",
SessyApiCommand.OTA_STATUS, 'status', device.check_ota,
coordinators[device.get_ota_status], 'status', device.check_ota,
entity_category=EntityCategory.DIAGNOSTIC
)
)

async_add_entities(buttons)

class SessyButton(SessyEntity, ButtonEntity):
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry, name: str,
cache_command: SessyApiCommand, cache_key: str, action_function: function,
class SessyButton(SessyCoordinatorEntity, ButtonEntity):
def __init__(self, hass: HomeAssistant, config_entry: SessyConfigEntry, name: str,
coordinator: SessyCoordinator, data_key: str, action_function: function,
device_class: ButtonDeviceClass = None, entity_category: EntityCategory = None,
transform_function: function = None, translation_key: str = None):

super().__init__(hass=hass, config_entry=config_entry, name=name,
cache_command=cache_command, cache_key=cache_key,
coordinator=coordinator, data_key=data_key,
transform_function=transform_function, translation_key=translation_key)

self.action_function = action_function
Expand Down
2 changes: 1 addition & 1 deletion custom_components/sessy/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# Static scan intervals
SCAN_INTERVAL_OTA_BUSY = timedelta(seconds=10)
SCAN_INTERVAL_OTA_CHECK = timedelta(hours=6)
SCAN_INTERVAL_SCHEDULE = {"minute":0,"second":0}
SCAN_INTERVAL_SCHEDULE = timedelta(hours=1)

SESSY_DEVICE = "sessy_device"
SERIAL_NUMBER = "serial_number"
Expand Down
29 changes: 16 additions & 13 deletions custom_components/sessy/coordinator.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""API Data Update Coordinator for Sessy"""
from __future__ import annotations

from datetime import timedelta
import logging

import async_timeout

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed
Expand All @@ -17,9 +17,9 @@
from sessypy.devices import SessyBattery, SessyCTMeter, SessyDevice, SessyMeter, SessyP1Meter
from sessypy.util import SessyConnectionException, SessyLoginException, SessyNotSupportedException

from .const import DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL_POWER, DOMAIN, ENTITY_ERROR_THRESHOLD, SCAN_INTERVAL_OTA_CHECK, SCAN_INTERVAL_SCHEDULE, SESSY_DEVICE, SESSY_DEVICE_INFO
from .const import DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL_POWER, ENTITY_ERROR_THRESHOLD, SCAN_INTERVAL_OTA_CHECK, SCAN_INTERVAL_SCHEDULE
from .models import SessyConfigEntry
from .util import SessyRuntimeData, get_nested_key
from .util import get_nested_key

_LOGGER = logging.getLogger(__name__)

Expand All @@ -33,21 +33,21 @@ async def setup_coordinators(hass, config_entry: SessyConfigEntry, device: Sessy
else: scan_interval_power = DEFAULT_SCAN_INTERVAL_POWER

# Device independent functions
coordinators.append(
coordinators.extend([
SessyCoordinator(hass, config_entry, device.get_ota_status),
SessyCoordinator(hass, config_entry, device.check_ota, SCAN_INTERVAL_OTA_CHECK), # Sessy will not check for updates automatically, poll at intervals
SessyCoordinator(hass, config_entry, device.get_system_info),
SessyCoordinator(hass, config_entry, device.get_network_status)
)
])


if isinstance(device, SessyBattery):
coordinators.append(
SessyCoordinator(hass, config_entry, device.get_dynamic_schedule, SCAN_INTERVAL_SCHEDULE),
coordinators.extend([
SessyCoordinator(hass, config_entry, device.get_dynamic_schedule), # TODO align to hour, only poll once every hour
SessyCoordinator(hass, config_entry, device.get_power_status, scan_interval_power),
SessyCoordinator(hass, config_entry, device.get_power_strategy),
SessyCoordinator(hass, config_entry, device.get_system_settings),
)
])


elif isinstance(device, SessyP1Meter):
Expand All @@ -71,8 +71,10 @@ async def setup_coordinators(hass, config_entry: SessyConfigEntry, device: Sessy
)

coordinators_dict = dict()
coordinator: SessyCoordinator
for coordinator in coordinators:
coordinators_dict[coordinator.name] = coordinator
await coordinator.async_config_entry_first_refresh()
coordinators_dict[coordinator._device_function] = coordinator

return coordinators_dict

Expand Down Expand Up @@ -112,6 +114,7 @@ def __init__(self, hass, config_entry, device_function: function, update_interva
always_update=False
)
self._device_function = device_function
self._raw_data = dict()

async def _async_setup(self):
"""Set up the coordinator
Expand All @@ -122,7 +125,7 @@ async def _async_setup(self):
This method will be called automatically during
coordinator.async_config_entry_first_refresh.
"""
await self._async_update_data(self)
await self._async_update_data()

async def _async_update_data(self):
"""Fetch data from API endpoint.
Expand All @@ -133,10 +136,10 @@ async def _async_update_data(self):
try:
# Note: asyncio.TimeoutError and aiohttp.ClientError are already
# handled by the data update coordinator.
async with async_timeout.timeout(10):
data = await self._device_function
async with async_timeout.timeout(self.update_interval.seconds / 2):
data = await self._device_function()

contexts: list[SessyEntityContext] = set(self.async_contexts)
contexts: list[SessyEntityContext] = set(self.async_contexts())
flattened_data = dict()
for context in contexts:
flattened_data[context.data_key] = context.apply(data)
Expand Down
9 changes: 3 additions & 6 deletions custom_components/sessy/models.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING
from dataclasses import dataclass, field

from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from sessypy.devices import SessyDevice

if TYPE_CHECKING:
from .coordinator import SessyCoordinator

type SessyConfigEntry = ConfigEntry[SessyRuntimeData]

@dataclass
class SessyRuntimeData:
device: SessyDevice = None
device_info: DeviceInfo = None
coordinators: dict[SessyCoordinator] = dict()
coordinators: dict[DataUpdateCoordinator] = field(default_factory=dict)
Loading

0 comments on commit a7a578b

Please sign in to comment.