"""Platform for Kostal Plenticore numbers.""" from __future__ import annotations from abc import ABC from dataclasses import dataclass from datetime import timedelta import logging from kostal.plenticore import SettingsData from homeassistant.components.number import ( NumberEntity, NumberEntityDescription, NumberMode, ) from homeassistant.components.sensor import SensorDeviceClass from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, POWER_WATT from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN from .helper import PlenticoreDataFormatter, SettingDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @dataclass class PlenticoreNumberEntityDescriptionMixin: """Define an entity description mixin for number entities.""" module_id: str data_id: str fmt_from: str fmt_to: str @dataclass class PlenticoreNumberEntityDescription( NumberEntityDescription, PlenticoreNumberEntityDescriptionMixin ): """Describes a Plenticore number entity.""" NUMBER_SETTINGS_DATA = [ PlenticoreNumberEntityDescription( key="battery_min_soc", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, icon="mdi:battery-negative", name="Battery min SoC", native_unit_of_measurement=PERCENTAGE, native_max_value=100, native_min_value=5, native_step=5, module_id="devices:local", data_id="Battery:MinSoc", fmt_from="format_round", fmt_to="format_round_back", ), PlenticoreNumberEntityDescription( key="battery_min_home_consumption", device_class=SensorDeviceClass.POWER, entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, name="Battery min Home Consumption", native_unit_of_measurement=POWER_WATT, native_max_value=38000, native_min_value=50, native_step=1, module_id="devices:local", data_id="Battery:MinHomeComsumption", fmt_from="format_round", fmt_to="format_round_back", ), ] async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Add Kostal Plenticore Number entities.""" plenticore = hass.data[DOMAIN][entry.entry_id] entities = [] available_settings_data = await plenticore.client.get_settings() settings_data_update_coordinator = SettingDataUpdateCoordinator( hass, _LOGGER, "Settings Data", timedelta(seconds=30), plenticore, ) for description in NUMBER_SETTINGS_DATA: if ( description.module_id not in available_settings_data or description.data_id not in ( setting.id for setting in available_settings_data[description.module_id] ) ): _LOGGER.debug( "Skipping non existing setting data %s/%s", description.module_id, description.data_id, ) continue setting_data = next( sd for sd in available_settings_data[description.module_id] if description.data_id == sd.id ) entities.append( PlenticoreDataNumber( settings_data_update_coordinator, entry.entry_id, entry.title, plenticore.device_info, description, setting_data, ) ) async_add_entities(entities) class PlenticoreDataNumber(CoordinatorEntity, NumberEntity, ABC): """Representation of a Kostal Plenticore Number entity.""" entity_description: PlenticoreNumberEntityDescription coordinator: SettingDataUpdateCoordinator def __init__( self, coordinator: SettingDataUpdateCoordinator, entry_id: str, platform_name: str, device_info: DeviceInfo, description: PlenticoreNumberEntityDescription, setting_data: SettingsData, ) -> None: """Initialize the Plenticore Number entity.""" super().__init__(coordinator) self.entity_description = description self.entry_id = entry_id self._attr_device_info = device_info self._attr_unique_id = f"{self.entry_id}_{self.module_id}_{self.data_id}" self._attr_name = f"{platform_name} {description.name}" self._attr_mode = NumberMode.BOX self._formatter = PlenticoreDataFormatter.get_method(description.fmt_from) self._formatter_back = PlenticoreDataFormatter.get_method(description.fmt_to) # overwrite from retrieved setting data if setting_data.min is not None: self._attr_native_min_value = self._formatter(setting_data.min) if setting_data.max is not None: self._attr_native_max_value = self._formatter(setting_data.max) @property def module_id(self) -> str: """Return the plenticore module id of this entity.""" return self.entity_description.module_id @property def data_id(self) -> str: """Return the plenticore data id for this entity.""" return self.entity_description.data_id @property def available(self) -> bool: """Return if entity is available.""" return ( super().available and self.coordinator.data is not None and self.module_id in self.coordinator.data and self.data_id in self.coordinator.data[self.module_id] ) async def async_added_to_hass(self) -> None: """Register this entity on the Update Coordinator.""" await super().async_added_to_hass() self.coordinator.start_fetch_data(self.module_id, self.data_id) async def async_will_remove_from_hass(self) -> None: """Unregister this entity from the Update Coordinator.""" self.coordinator.stop_fetch_data(self.module_id, self.data_id) await super().async_will_remove_from_hass() @property def native_value(self) -> float | None: """Return the current value.""" if self.available: raw_value = self.coordinator.data[self.module_id][self.data_id] return self._formatter(raw_value) return None async def async_set_native_value(self, value: float) -> None: """Set a new value.""" str_value = self._formatter_back(value) await self.coordinator.async_write_data( self.module_id, {self.data_id: str_value} ) await self.coordinator.async_refresh()