338 lines
11 KiB
Python
338 lines
11 KiB
Python
""" A bonary sensor entity that holds the state of each managed_device """
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, STATE_ON
|
|
from homeassistant.core import callback, HomeAssistant, State, Event
|
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
from homeassistant.helpers.restore_state import RestoreEntity
|
|
from homeassistant.components.switch import (
|
|
SwitchEntity,
|
|
)
|
|
|
|
from homeassistant.helpers.entity_platform import (
|
|
AddEntitiesCallback,
|
|
)
|
|
|
|
from homeassistant.helpers.event import (
|
|
async_track_state_change_event,
|
|
)
|
|
|
|
from .const import (
|
|
DOMAIN,
|
|
name_to_unique_id,
|
|
get_tz,
|
|
EVENT_TYPE_SOLAR_OPTIMIZER_ENABLE_STATE_CHANGE,
|
|
)
|
|
from .coordinator import SolarOptimizerCoordinator
|
|
from .managed_device import ManagedDevice
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant, _, async_add_entities: AddEntitiesCallback
|
|
) -> None:
|
|
"""Setup the entries of type Binary sensor, one for each ManagedDevice"""
|
|
_LOGGER.debug("Calling switch.async_setup_entry")
|
|
|
|
coordinator: SolarOptimizerCoordinator = hass.data[DOMAIN]["coordinator"]
|
|
|
|
entities = []
|
|
for _, device in enumerate(coordinator.devices):
|
|
entity = ManagedDeviceSwitch(
|
|
coordinator,
|
|
hass,
|
|
device.name,
|
|
name_to_unique_id(device.name),
|
|
device.entity_id,
|
|
)
|
|
if entity is not None:
|
|
entities.append(entity)
|
|
|
|
entity = ManagedDeviceEnable(hass, device)
|
|
if entity is not None:
|
|
entities.append(entity)
|
|
|
|
async_add_entities(entities)
|
|
|
|
|
|
class ManagedDeviceSwitch(CoordinatorEntity, SwitchEntity):
|
|
"""The entity holding the algorithm calculation"""
|
|
|
|
_entity_component_unrecorded_attributes = (
|
|
SwitchEntity._entity_component_unrecorded_attributes.union(
|
|
frozenset(
|
|
{
|
|
"is_enabled",
|
|
"is_active",
|
|
"is_waiting",
|
|
"is_usable",
|
|
"can_change_power",
|
|
"duration_sec",
|
|
"duration_power_sec",
|
|
"power_min",
|
|
"power_max",
|
|
"next_date_available",
|
|
"next_date_available_power",
|
|
"battery_soc_threshold",
|
|
"battery_soc",
|
|
}
|
|
)
|
|
)
|
|
)
|
|
|
|
def __init__(self, coordinator, hass, name, idx, entity_id):
|
|
_LOGGER.debug("Adding ManagedDeviceSwitch for %s", name)
|
|
super().__init__(coordinator, context=idx)
|
|
self._hass: HomeAssistant = hass
|
|
self.idx = idx
|
|
self._attr_name = "Solar Optimizer " + name
|
|
self._attr_unique_id = "solar_optimizer_" + idx
|
|
self._entity_id = entity_id
|
|
|
|
# Try to get the state if it exists
|
|
device: ManagedDevice = None
|
|
if (device := coordinator.get_device_by_unique_id(self.idx)) is not None:
|
|
self._attr_is_on = device.is_active
|
|
else:
|
|
self._attr_is_on = None
|
|
|
|
async def async_added_to_hass(self) -> None:
|
|
"""The entity have been added to hass, listen to state change of the underlying entity"""
|
|
await super().async_added_to_hass()
|
|
|
|
# Arme l'écoute de la première entité
|
|
listener_cancel = async_track_state_change_event(
|
|
self.hass,
|
|
[self._entity_id],
|
|
self._on_state_change,
|
|
)
|
|
# desarme le timer lors de la destruction de l'entité
|
|
self.async_on_remove(listener_cancel)
|
|
|
|
# desarme le timer lors de la destruction de l'entité
|
|
self.async_on_remove(
|
|
self._hass.bus.async_listen(
|
|
event_type=EVENT_TYPE_SOLAR_OPTIMIZER_ENABLE_STATE_CHANGE,
|
|
listener=self._on_enable_state_change,
|
|
)
|
|
)
|
|
|
|
@callback
|
|
async def _on_enable_state_change(self, event: Event) -> None:
|
|
"""Triggered when the ManagedDevice enable state have change"""
|
|
|
|
# is it for me ?
|
|
if (
|
|
not event.data
|
|
or (device_id := event.data.get("device_unique_id")) != self.idx
|
|
):
|
|
return
|
|
|
|
# search for coordinator and device
|
|
if not self.coordinator or not (
|
|
device := self.coordinator.get_device_by_unique_id(device_id)
|
|
):
|
|
return
|
|
|
|
_LOGGER.info(
|
|
"Changing enabled state for %s to %s", device_id, device.is_enabled
|
|
)
|
|
|
|
self.update_custom_attributes(device)
|
|
self.async_write_ha_state()
|
|
|
|
@callback
|
|
async def _on_state_change(self, event: Event) -> None:
|
|
"""The entity have change its state"""
|
|
_LOGGER.info(
|
|
"Appel de on_state_change à %s avec l'event %s", datetime.now(), event
|
|
)
|
|
|
|
if not event.data:
|
|
return
|
|
|
|
# search for coordinator and device
|
|
if not self.coordinator or not (
|
|
device := self.coordinator.get_device_by_unique_id(self.idx)
|
|
):
|
|
return
|
|
|
|
new_state: State = event.data.get("new_state")
|
|
# old_state: State = event.data.get("old_state")
|
|
|
|
if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
|
_LOGGER.debug("Pas d'état disponible. Evenement ignoré")
|
|
return
|
|
|
|
# On recherche la date de l'event pour la stocker dans notre état
|
|
new_state = new_state.state == STATE_ON
|
|
if new_state == self._attr_is_on:
|
|
return
|
|
|
|
self._attr_is_on = new_state
|
|
# On sauvegarde le nouvel état
|
|
self.update_custom_attributes(device)
|
|
self.async_write_ha_state()
|
|
|
|
def update_custom_attributes(self, device):
|
|
"""Add some custom attributes to the entity"""
|
|
current_tz = get_tz(self._hass)
|
|
self._attr_extra_state_attributes: dict(str, str) = {
|
|
"is_enabled": device.is_enabled,
|
|
"is_active": device.is_active,
|
|
"is_waiting": device.is_waiting,
|
|
"is_usable": device.is_usable,
|
|
"can_change_power": device.can_change_power,
|
|
"current_power": device.current_power,
|
|
"requested_power": device.requested_power,
|
|
"duration_sec": device.duration_sec,
|
|
"duration_power_sec": device.duration_power_sec,
|
|
"power_min": device.power_min,
|
|
"power_max": device.power_max,
|
|
"next_date_available": device.next_date_available.astimezone(
|
|
current_tz
|
|
).isoformat(),
|
|
"next_date_available_power": device.next_date_available_power.astimezone(
|
|
current_tz
|
|
).isoformat(),
|
|
"battery_soc_threshold": device._battery_soc_threshold,
|
|
"battery_soc": device._battery_soc,
|
|
}
|
|
|
|
@callback
|
|
def _handle_coordinator_update(self) -> None:
|
|
"""Handle updated data from the coordinator."""
|
|
_LOGGER.debug("Calling _handle_coordinator_update for %s", self._attr_name)
|
|
|
|
if not self.coordinator or not self.coordinator.data:
|
|
_LOGGER.debug("No coordinator found or no data...")
|
|
return
|
|
|
|
device: ManagedDevice = self.coordinator.data.get(self.idx)
|
|
if not device:
|
|
# it is possible to not have device in coordinator update (if device is not enabled)
|
|
_LOGGER.debug("No device %s found ...", self.idx)
|
|
return
|
|
|
|
self._attr_is_on = device.is_active
|
|
self.update_custom_attributes(device)
|
|
self.async_write_ha_state()
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Turn the entity on."""
|
|
if not self.coordinator or not self.coordinator.data:
|
|
return
|
|
|
|
_LOGGER.info("Turn_on Solar Optimizer switch %s", self._attr_name)
|
|
# search for coordinator and device
|
|
if not self.coordinator or not (
|
|
device := self.coordinator.get_device_by_unique_id(self.idx)
|
|
):
|
|
return
|
|
|
|
if not self._attr_is_on:
|
|
await device.activate()
|
|
self._attr_is_on = True
|
|
self.update_custom_attributes(device)
|
|
self.async_write_ha_state()
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Turn the entity on."""
|
|
if not self.coordinator or not self.coordinator.data:
|
|
return
|
|
|
|
_LOGGER.info("Turn_on Solar Optimizer switch %s", self._attr_name)
|
|
# search for coordinator and device
|
|
if not self.coordinator or not (
|
|
device := self.coordinator.get_device_by_unique_id(self.idx)
|
|
):
|
|
return
|
|
|
|
if self._attr_is_on:
|
|
await device.deactivate()
|
|
self._attr_is_on = False
|
|
self.update_custom_attributes(device)
|
|
self.async_write_ha_state()
|
|
|
|
@property
|
|
def device_info(self):
|
|
# Retournez des informations sur le périphérique associé à votre entité
|
|
return {
|
|
"identifiers": {(DOMAIN, "solar_optimizer_device")},
|
|
"name": "Solar Optimizer",
|
|
# Autres attributs du périphérique ici
|
|
}
|
|
|
|
@property
|
|
def get_attr_extra_state_attributes(self):
|
|
"""Get the extra state attributes for the entity"""
|
|
return self._attr_extra_state_attributes
|
|
|
|
|
|
class ManagedDeviceEnable(SwitchEntity, RestoreEntity):
|
|
"""The that enables the ManagedDevice optimisation with"""
|
|
|
|
_device: ManagedDevice
|
|
|
|
def __init__(self, hass: HomeAssistant, device: ManagedDevice):
|
|
self._hass: HomeAssistant = hass
|
|
self._device = device
|
|
self._attr_name = "Enable Solar Optimizer " + device.name
|
|
self._attr_unique_id = "solar_optimizer_enable_" + name_to_unique_id(
|
|
device.name
|
|
)
|
|
self._attr_is_on = True
|
|
|
|
@property
|
|
def device_info(self):
|
|
# Retournez des informations sur le périphérique associé à votre entité
|
|
return {
|
|
"identifiers": {(DOMAIN, "solar_optimizer_device")},
|
|
"name": "Solar Optimizer",
|
|
# Autres attributs du périphérique ici
|
|
}
|
|
|
|
@property
|
|
def icon(self) -> str | None:
|
|
return "mdi:check"
|
|
|
|
async def async_added_to_hass(self):
|
|
await super().async_added_to_hass()
|
|
|
|
# Récupérer le dernier état sauvegardé de l'entité
|
|
last_state = await self.async_get_last_state()
|
|
|
|
# Si l'état précédent existe, vous pouvez l'utiliser
|
|
if last_state is not None:
|
|
self._attr_is_on = last_state.state == "on"
|
|
else:
|
|
# Si l'état précédent n'existe pas, initialisez l'état comme vous le souhaitez
|
|
self._attr_is_on = True
|
|
|
|
# this breaks the start of integration
|
|
self.update_device_enabled()
|
|
|
|
@callback
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Turn the entity on."""
|
|
self._attr_is_on = True
|
|
self.async_write_ha_state()
|
|
self.update_device_enabled()
|
|
|
|
@callback
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Turn the entity off."""
|
|
self._attr_is_on = False
|
|
self.async_write_ha_state()
|
|
self.update_device_enabled()
|
|
|
|
def update_device_enabled(self) -> None:
|
|
"""Update the device is enabled flag"""
|
|
if not self._device:
|
|
return
|
|
|
|
self._device.set_enable(self._attr_is_on)
|