diff --git a/custom_components/askoheat/api.py b/custom_components/askoheat/api.py index dc9e62a..2489a74 100644 --- a/custom_components/askoheat/api.py +++ b/custom_components/askoheat/api.py @@ -6,16 +6,27 @@ from typing import TYPE_CHECKING, Any, TypeVar import numpy as np +from numpy import number from pymodbus.client import AsyncModbusTcpClient as ModbusClient +from custom_components.askoheat.api_conf_desc import CONF_REGISTER_BLOCK_DESCRIPTOR +from custom_components.askoheat.api_desc import ( + ByteRegisterInputDescriptor, + FlagRegisterInputDescriptor, + Float32RegisterInputDescriptor, + RegisterBlockDescriptor, + RegisterInputDescriptor, + SignedIntRegisterInputDescriptor, + StringRegisterInputDescriptor, + UnsignedIntRegisterInputDescriptor, +) +from custom_components.askoheat.api_ema_desc import EMA_REGISTER_BLOCK_DESCRIPTOR from custom_components.askoheat.const import ( LOGGER, Baurate, BinarySensorAttrKey, EnergyMeterType, - NumberAttrKey, SelectAttrKey, - SensorAttrKey, SwitchAttrKey, TextAttrKey, TimeAttrKey, @@ -57,17 +68,21 @@ def close(self) -> None: async def async_read_ema_data(self) -> AskoheatDataBlock: """Read EMA states.""" - # http://www.download.askoma.com/askofamily_plus/modbus/askoheat-modbus.html#EM_Block - data = await self.async_read_input_registers_data(300, 37) + data = await self.async_read_input_registers_data( + EMA_REGISTER_BLOCK_DESCRIPTOR.starting_register, + EMA_REGISTER_BLOCK_DESCRIPTOR.number_of_registers, + ) LOGGER.debug("async_read_ema_data %s", data) - return self._map_ema_data(data) + return self.__map_data(EMA_REGISTER_BLOCK_DESCRIPTOR, data) async def async_read_config_data(self) -> AskoheatDataBlock: """Read EMA states.""" - # http://www.download.askoma.com/askofamily_plus/modbus/askoheat-modbus.html#Configuration_Block - data = await self.async_read_holding_registers_data(500, 100) + data = await self.async_read_holding_registers_data( + CONF_REGISTER_BLOCK_DESCRIPTOR.starting_register, + CONF_REGISTER_BLOCK_DESCRIPTOR.number_of_registers, + ) LOGGER.debug("async_read_config_data %s", data) - return self._map_config_data(data) + return self.__map_data(CONF_REGISTER_BLOCK_DESCRIPTOR, data) async def async_read_input_registers_data( self, address: int, count: int @@ -89,176 +104,54 @@ async def async_read_holding_registers_data( return await self._client.read_holding_registers(address=address, count=count) - def _map_ema_data(self, data: ModbusPDU) -> AskoheatDataBlock: - """Map modbus result of ema data block.""" + def __map_data( + self, descr: RegisterBlockDescriptor, data: ModbusPDU + ) -> AskoheatDataBlock: + binary_sensors = { + k: v + for k, v in { + item.key: _read_register_boolean_input(data, item.api_descriptor) # type: ignore # noqa: PGH003 + for item in descr.binary_sensors + }.items() + if v is not None + } + number_inputs = { + k: v + for k, v in { + item.key: _read_register_number_input(data, item.api_descriptor) # type: ignore # noqa: PGH003 + for item in descr.number_inputs + }.items() + if v is not None + } + sensors = { + k: v + for k, v in { + item.key: _read_register_input(data, item.api_descriptor) # type: ignore # noqa: PGH003 + for item in descr.sensors + }.items() + if v is not None + } + switches = { + k: v + for k, v in { + item.key: _read_register_boolean_input(data, item.api_descriptor) # type: ignore # noqa: PGH003 + for item in descr.switches + }.items() + if v is not None + } return AskoheatDataBlock( - binary_sensors=self._map_register_to_status(data.registers[16]), - sensors={ - SensorAttrKey.HEATER_LOAD: _read_uint16(data.registers[17]), - SensorAttrKey.LOAD_SETPOINT_VALUE: _read_int16(data.registers[19]), - SensorAttrKey.LOAD_FEEDIN_VALUE: _read_int16(data.registers[20]), - SensorAttrKey.ANALOG_INPUT_VALUE: _read_float32(data.registers[23:25]), - SensorAttrKey.INTERNAL_TEMPERATUR_SENSOR_VALUE: _read_float32( - data.registers[25:27] - ), - SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR1_VALUE: _read_float32( - data.registers[27:29] - ), - SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR2_VALUE: _read_float32( - data.registers[29:31] - ), - SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR3_VALUE: _read_float32( - data.registers[31:33] - ), - SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR4_VALUE: _read_float32( - data.registers[33:35] - ), - }, - switches=self._map_register_to_heater_step(data.registers[18]), + binary_sensors=binary_sensors, + sensors=sensors, + switches=switches, + number_inputs=number_inputs, ) - def _map_config_data(self, data: ModbusPDU) -> AskoheatDataBlock: + def __map_config_data(self, data: ModbusPDU) -> AskoheatDataBlock: """Map modbus result of config data block.""" return AskoheatDataBlock( - number_inputs={ - NumberAttrKey.CON_RELAY_SEC_COUNT_SECONDS: _read_uint16( - data.registers[0] - ), - NumberAttrKey.CON_PUMP_SEC_COUNT_SECONDS: _read_uint16( - data.registers[1] - ), - NumberAttrKey.CON_AUTO_HEATER_OFF_MINUTES: _read_uint16( - data.registers[3] - ), - NumberAttrKey.CON_CASCADE_PRIORIZATION: _read_byte(data.registers[5]), - NumberAttrKey.CON_HEATBUFFER_VOLUME: _read_uint16(data.registers[7]), - NumberAttrKey.CON_LEGIO_PROTECTION_TEMPERATURE: _read_byte( - data.registers[10] - ), - NumberAttrKey.CON_LEGIO_PROTECTION_HEATUP_MINUTES: _read_uint16( - data.registers[11] - ), - NumberAttrKey.CON_NUMBER_OF_HOUSEHOLD_MEMBERS: _read_byte( - data.registers[21] - ), - NumberAttrKey.CON_LOAD_FEEDIN_DELAY_SECONDS: _read_uint16( - data.registers[39] - ), - NumberAttrKey.CON_LOAD_FEEDIN_BASIC_ENERGY_LEVEL: _read_uint16( - data.registers[40] - ), - NumberAttrKey.CON_TIMEZONE_OFFSET: _read_int16(data.registers[41]), - NumberAttrKey.CON_RTU_SLAVE_ID: _read_byte(data.registers[50]), - NumberAttrKey.CON_ANALOG_INPUT_HYSTERESIS: _read_float32( - data.registers[56:58] - ), - # Analog 0 - NumberAttrKey.CON_ANALOG_INPUT_0_THRESHOLD: _read_float32( - data.registers[58:60] - ), - NumberAttrKey.CON_ANALOG_INPUT_0_THRESHOLD_STEP: _read_byte( - data.registers[60] - ), - NumberAttrKey.CON_ANALOG_INPUT_0_THRESHOLD_TEMPERATURE: _read_byte( - data.registers[61] - ), - # Analog 1 - NumberAttrKey.CON_ANALOG_INPUT_1_THRESHOLD: _read_float32( - data.registers[62:64] - ), - NumberAttrKey.CON_ANALOG_INPUT_1_THRESHOLD_STEP: _read_byte( - data.registers[64] - ), - NumberAttrKey.CON_ANALOG_INPUT_1_THRESHOLD_TEMPERATURE: _read_byte( - data.registers[65] - ), - # Analog 2 - NumberAttrKey.CON_ANALOG_INPUT_2_THRESHOLD: _read_float32( - data.registers[66:68] - ), - NumberAttrKey.CON_ANALOG_INPUT_2_THRESHOLD_STEP: _read_byte( - data.registers[68] - ), - NumberAttrKey.CON_ANALOG_INPUT_2_THRESHOLD_TEMPERATURE: _read_byte( - data.registers[69] - ), - # Analog 3 - NumberAttrKey.CON_ANALOG_INPUT_3_THRESHOLD: _read_float32( - data.registers[70:72] - ), - NumberAttrKey.CON_ANALOG_INPUT_3_THRESHOLD_STEP: _read_byte( - data.registers[72] - ), - NumberAttrKey.CON_ANALOG_INPUT_3_THRESHOLD_TEMPERATURE: _read_byte( - data.registers[73] - ), - # Analog 4 - NumberAttrKey.CON_ANALOG_INPUT_4_THRESHOLD: _read_float32( - data.registers[74:76] - ), - NumberAttrKey.CON_ANALOG_INPUT_4_THRESHOLD_STEP: _read_byte( - data.registers[76] - ), - NumberAttrKey.CON_ANALOG_INPUT_4_THRESHOLD_TEMPERATURE: _read_byte( - data.registers[77] - ), - # Analog 5 - NumberAttrKey.CON_ANALOG_INPUT_5_THRESHOLD: _read_float32( - data.registers[78:80] - ), - NumberAttrKey.CON_ANALOG_INPUT_5_THRESHOLD_STEP: _read_byte( - data.registers[80] - ), - NumberAttrKey.CON_ANALOG_INPUT_5_THRESHOLD_TEMPERATURE: _read_byte( - data.registers[81] - ), - # Analog 6 - NumberAttrKey.CON_ANALOG_INPUT_6_THRESHOLD: _read_float32( - data.registers[82:84] - ), - NumberAttrKey.CON_ANALOG_INPUT_6_THRESHOLD_STEP: _read_byte( - data.registers[84] - ), - NumberAttrKey.CON_ANALOG_INPUT_6_THRESHOLD_TEMPERATURE: _read_byte( - data.registers[65] - ), - # Analog 7 - NumberAttrKey.CON_ANALOG_INPUT_7_THRESHOLD: _read_float32( - data.registers[86:88] - ), - NumberAttrKey.CON_ANALOG_INPUT_7_THRESHOLD_STEP: _read_byte( - data.registers[88] - ), - NumberAttrKey.CON_ANALOG_INPUT_7_THRESHOLD_TEMPERATURE: _read_byte( - data.registers[89] - ), - NumberAttrKey.CON_HEAT_PUMP_REQUEST_OFF_STEP: _read_byte( - data.registers[90] - ), - NumberAttrKey.CON_HEAT_PUMP_REQUEST_ON_STEP: _read_byte( - data.registers[91] - ), - NumberAttrKey.CON_EMERGENCY_MODE_ON_STOP: _read_byte( - data.registers[92] - ), - NumberAttrKey.CON_TEMPERATURE_HYSTERESIS: _read_byte( - data.registers[93] - ), - NumberAttrKey.CON_MINIMAL_TEMPERATURE: _read_byte(data.registers[95]), - NumberAttrKey.CON_SET_HEATER_STEP_TEMPERATURE_LIMIT: _read_byte( - data.registers[96] - ), - NumberAttrKey.CON_LOAD_FEEDIN_OR_SETPOINT_TEMPERATURE_LIMIT: _read_byte( - data.registers[97] - ), - NumberAttrKey.CON_LOW_TARIFF_TEMPERATURE_LIMIT: _read_byte( - data.registers[98] - ), - NumberAttrKey.CON_HEATPUMP_REQUEST_TEMPERATURE_LIMIT: _read_byte( - data.registers[99] - ), - }, + number_inputs={}, switches={ + # TODO: Move/merge to api_conf_desc.py # input settings register # low byte SwitchAttrKey.CON_MISSING_CURRENT_FLOW_TRIGGERS_ERROR: _read_flag( @@ -454,7 +347,7 @@ def _map_config_data(self, data: ModbusPDU) -> AskoheatDataBlock: minute=_read_byte(data.registers[55]), ), }, - text_intputs={ + text_inputs={ TextAttrKey.CON_INFO_STRING: _read_str(data.registers[22:38]), }, select_inputs={ @@ -496,15 +389,61 @@ def _map_register_to_status( BinarySensorAttrKey.ERROR_OCCURED: _read_flag(register_value, 15), } - def _map_register_to_heater_step( - self, register_value: int - ) -> dict[SwitchAttrKey, bool]: - """Map modbus register to status class.""" - return { - SwitchAttrKey.EMA_SET_HEATER_STEP_HEATER1: _read_flag(register_value, 0), - SwitchAttrKey.EMA_SET_HEATER_STEP_HEATER2: _read_flag(register_value, 1), - SwitchAttrKey.EMA_SET_HEATER_STEP_HEATER3: _read_flag(register_value, 2), - } + +def _read_register_input(data: ModbusPDU, desc: RegisterInputDescriptor) -> object: + match desc: + case FlagRegisterInputDescriptor(starting_register, bit): + result = _read_flag(data.registers[starting_register], bit) + case ByteRegisterInputDescriptor(starting_register): + result = _read_byte(data.registers[starting_register]) + case UnsignedIntRegisterInputDescriptor(starting_register): + result = _read_uint16(data.registers[starting_register]) + case SignedIntRegisterInputDescriptor(starting_register): + result = _read_int16(data.registers[starting_register]) + case Float32RegisterInputDescriptor(starting_register): + result = _read_float32( + data.registers[starting_register : starting_register + 2] + ) + case StringRegisterInputDescriptor(starting_register, number_of_bytes): + result = _read_str( + data.registers[ + starting_register : starting_register + number_of_bytes + 1 + ] + ) + case _: + LOGGER.error("Cannot read number input from descriptor %r", desc) + result = None + return result + + +def _read_register_boolean_input( + data: ModbusPDU, desc: RegisterInputDescriptor +) -> bool | None: + result = _read_register_input(data, desc) + if isinstance(result, bool): + return result + + LOGGER.error( + "Cannot read bool input from descriptor %r, unsupported value %r", + desc, + result, + ) + return None + + +def _read_register_number_input( + data: ModbusPDU, desc: RegisterInputDescriptor +) -> number | None: + result = _read_register_input(data, desc) + if isinstance(result, number): + return result + + LOGGER.error( + "Cannot read number input from descriptor %r, unsupported value %r", + desc, + result, + ) + return None def _read_time(register_value_hours: int, register_value_minutes: int) -> time | None: diff --git a/custom_components/askoheat/api_conf_desc.py b/custom_components/askoheat/api_conf_desc.py new file mode 100644 index 0000000..7046dac --- /dev/null +++ b/custom_components/askoheat/api_conf_desc.py @@ -0,0 +1,492 @@ +"""Configuration block Api descriptor classes.""" +# http://www.download.askoma.com/askofamily_plus/modbus/askoheat-modbus.html#Configuration_Block + +from __future__ import annotations + +from homeassistant.components.number import NumberMode +from homeassistant.const import ( + EntityCategory, + UnitOfElectricPotential, + UnitOfPower, + UnitOfTemperature, + UnitOfTime, + UnitOfVolume, +) + +from custom_components.askoheat.api_desc import ( + ByteRegisterInputDescriptor, + Float32RegisterInputDescriptor, + RegisterBlockDescriptor, + SignedIntRegisterInputDescriptor, + UnsignedIntRegisterInputDescriptor, +) +from custom_components.askoheat.const import ( + NumberAttrKey, +) +from custom_components.askoheat.model import ( + AskoheatNumberEntityDescription, +) + +CONF_REGISTER_BLOCK_DESCRIPTOR = RegisterBlockDescriptor( + starting_register=500, + number_of_registers=100, + number_inputs=[ + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_RELAY_SEC_COUNT_SECONDS, + native_min_value=0, + native_max_value=16, + native_default_value=5, + native_unit_of_measurement=UnitOfTime.SECONDS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:timelapse", + api_descriptor=UnsignedIntRegisterInputDescriptor(0), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_PUMP_SEC_COUNT_SECONDS, + native_min_value=0, + native_max_value=240, + native_default_value=30, + native_unit_of_measurement=UnitOfTime.SECONDS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:timelapse", + api_descriptor=UnsignedIntRegisterInputDescriptor(1), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_AUTO_HEATER_OFF_MINUTES, + native_min_value=2, + native_max_value=10080, + native_unit_of_measurement=UnitOfTime.MINUTES, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:timer-outline", + api_descriptor=UnsignedIntRegisterInputDescriptor(3), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_CASCADE_PRIORIZATION, + native_min_value=0, + native_max_value=255, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:priority-high", + api_descriptor=ByteRegisterInputDescriptor(5), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_HEATBUFFER_VOLUME, + native_min_value=0, + native_max_value=1000, + native_unit_of_measurement=UnitOfVolume.LITERS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:waves", + api_descriptor=UnsignedIntRegisterInputDescriptor(7), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_LEGIO_PROTECTION_TEMPERATURE, + native_min_value=50, + native_max_value=65, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:water-thermometer", + api_descriptor=ByteRegisterInputDescriptor(10), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_LEGIO_PROTECTION_HEATUP_MINUTES, + native_min_value=0, + native_max_value=1440, + native_default_value=240, + native_unit_of_measurement=UnitOfTime.MINUTES, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:timelapse", + api_descriptor=UnsignedIntRegisterInputDescriptor(11), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_NUMBER_OF_HOUSEHOLD_MEMBERS, + native_min_value=1, + native_max_value=255, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:human-male-female-child", + api_descriptor=ByteRegisterInputDescriptor(21), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_LOAD_FEEDIN_DELAY_SECONDS, + native_min_value=0, + native_max_value=120, + native_unit_of_measurement=UnitOfTime.SECONDS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:timer-sand", + api_descriptor=UnsignedIntRegisterInputDescriptor(39), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_LOAD_FEEDIN_BASIC_ENERGY_LEVEL, + native_min_value=0, + native_max_value=10000, + native_unit_of_measurement=UnitOfPower.WATT, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:lightning-bolt", + api_descriptor=UnsignedIntRegisterInputDescriptor(40), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_TIMEZONE_OFFSET, + native_min_value=-12, + native_max_value=12, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:map-clock", + api_descriptor=SignedIntRegisterInputDescriptor(41), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_RTU_SLAVE_ID, + native_min_value=0, + native_max_value=240, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:identifier", + api_descriptor=ByteRegisterInputDescriptor(50), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_HYSTERESIS, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:slope-downhill", + api_descriptor=Float32RegisterInputDescriptor(56), + ), + # Analog 0 + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_0_THRESHOLD, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:slope-uphill", + api_descriptor=Float32RegisterInputDescriptor(58), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_0_THRESHOLD_STEP, + native_min_value=0, + native_max_value=7, + native_step=1, + native_default_value=7, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:gauge", + api_descriptor=ByteRegisterInputDescriptor(60), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_0_THRESHOLD_TEMPERATURE, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer", + api_descriptor=ByteRegisterInputDescriptor(61), + ), + # Analog 1 + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_1_THRESHOLD, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:slope-uphill", + api_descriptor=Float32RegisterInputDescriptor(62), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_1_THRESHOLD_STEP, + native_min_value=0, + native_max_value=7, + native_step=1, + native_default_value=7, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:stairs", + api_descriptor=ByteRegisterInputDescriptor(64), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_1_THRESHOLD_TEMPERATURE, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer", + api_descriptor=ByteRegisterInputDescriptor(65), + ), + # Analog 2 + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_2_THRESHOLD, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:slope-uphill", + api_descriptor=Float32RegisterInputDescriptor(66), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_2_THRESHOLD_STEP, + native_min_value=0, + native_max_value=7, + native_step=1, + native_default_value=7, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:stairs", + api_descriptor=ByteRegisterInputDescriptor(68), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_2_THRESHOLD_TEMPERATURE, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer", + api_descriptor=ByteRegisterInputDescriptor(69), + ), + # Analog 3 + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_3_THRESHOLD, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:slope-uphill", + api_descriptor=Float32RegisterInputDescriptor(70), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_3_THRESHOLD_STEP, + native_min_value=0, + native_max_value=7, + native_step=1, + native_default_value=7, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:stairs", + api_descriptor=ByteRegisterInputDescriptor(72), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_3_THRESHOLD_TEMPERATURE, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer", + api_descriptor=ByteRegisterInputDescriptor(73), + ), + # Analog 4 + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_4_THRESHOLD, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:slope-uphill", + api_descriptor=Float32RegisterInputDescriptor(74), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_4_THRESHOLD_STEP, + native_min_value=0, + native_max_value=7, + native_step=1, + native_default_value=7, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:stairs", + api_descriptor=ByteRegisterInputDescriptor(76), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_4_THRESHOLD_TEMPERATURE, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer", + api_descriptor=ByteRegisterInputDescriptor(77), + ), + # Analog 5 + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_5_THRESHOLD, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:slope-uphill", + api_descriptor=Float32RegisterInputDescriptor(78), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_5_THRESHOLD_STEP, + native_min_value=0, + native_max_value=7, + native_step=1, + native_default_value=7, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:stairs", + api_descriptor=ByteRegisterInputDescriptor(80), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_5_THRESHOLD_TEMPERATURE, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer", + api_descriptor=ByteRegisterInputDescriptor(81), + ), + # Analog 6 + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_6_THRESHOLD, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:slope-uphill", + api_descriptor=Float32RegisterInputDescriptor(82), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_6_THRESHOLD_STEP, + native_min_value=0, + native_max_value=7, + native_step=1, + native_default_value=7, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:stairs", + api_descriptor=ByteRegisterInputDescriptor(84), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_6_THRESHOLD_TEMPERATURE, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer", + api_descriptor=ByteRegisterInputDescriptor(85), + ), + # Analog 7 + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_7_THRESHOLD, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:slope-uphill", + api_descriptor=Float32RegisterInputDescriptor(86), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_7_THRESHOLD_STEP, + native_min_value=0, + native_max_value=7, + native_step=1, + native_default_value=7, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:stairs", + api_descriptor=ByteRegisterInputDescriptor(88), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_ANALOG_INPUT_7_THRESHOLD_TEMPERATURE, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer", + api_descriptor=ByteRegisterInputDescriptor(89), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_HEAT_PUMP_REQUEST_OFF_STEP, + native_min_value=0, + native_max_value=7, + native_step=1, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:stairs", + api_descriptor=ByteRegisterInputDescriptor(90), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_HEAT_PUMP_REQUEST_ON_STEP, + native_min_value=0, + native_max_value=7, + native_step=1, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:stairs", + api_descriptor=ByteRegisterInputDescriptor(91), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_EMERGENCY_MODE_ON_STOP, + native_min_value=0, + native_max_value=7, + native_step=1, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.SLIDER, + icon="mdi:stairs", + api_descriptor=ByteRegisterInputDescriptor(92), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_TEMPERATURE_HYSTERESIS, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer", + api_descriptor=ByteRegisterInputDescriptor(93), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_MINIMAL_TEMPERATURE, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer-minus", + api_descriptor=ByteRegisterInputDescriptor(95), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_SET_HEATER_STEP_TEMPERATURE_LIMIT, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer-chevron-up", + api_descriptor=ByteRegisterInputDescriptor(96), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_LOAD_FEEDIN_OR_SETPOINT_TEMPERATURE_LIMIT, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer-chevron-up", + api_descriptor=ByteRegisterInputDescriptor(97), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_LOW_TARIFF_TEMPERATURE_LIMIT, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer-chevron-up", + api_descriptor=ByteRegisterInputDescriptor(98), + ), + AskoheatNumberEntityDescription( + key=NumberAttrKey.CON_HEATPUMP_REQUEST_TEMPERATURE_LIMIT, + native_min_value=0, + native_max_value=95, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.CONFIG, + mode=NumberMode.BOX, + icon="mdi:thermometer-chevron-up", + api_descriptor=ByteRegisterInputDescriptor(99), + ), + ], +) diff --git a/custom_components/askoheat/api_desc.py b/custom_components/askoheat/api_desc.py new file mode 100644 index 0000000..20777d5 --- /dev/null +++ b/custom_components/askoheat/api_desc.py @@ -0,0 +1,88 @@ +"""Api descriptor classes.""" + +from __future__ import annotations + +import typing +from abc import ABC +from dataclasses import dataclass, field +from enum import StrEnum +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from custom_components.askoheat.model import ( + AskoheatBinarySensorEntityDescription, + AskoheatNumberEntityDescription, + AskoheatSensorEntityDescription, + AskoheatSwitchEntityDescription, + ) + + +class SwitchAttrKey(StrEnum): + """Askoheat binary switch attribute keys.""" + + +@dataclass(frozen=True) +class RegisterInputDescriptor(ABC): # noqa: B024 + """Description of a register based input.""" + + starting_register: typing.Final[int] = field() + + +@dataclass(frozen=True) +class FlagRegisterInputDescriptor(RegisterInputDescriptor): + """Input register representing a a flag as a bit mask of an int value.""" + + bit: int + + +@dataclass(frozen=True) +class ByteRegisterInputDescriptor(RegisterInputDescriptor): + """Input register representing a byte.""" + + +@dataclass(frozen=True) +class Float32RegisterInputDescriptor(RegisterInputDescriptor): + """Input register representing a float32.""" + + +@dataclass(frozen=True) +class SignedIntRegisterInputDescriptor(RegisterInputDescriptor): + """Input register representing a signed int.""" + + +@dataclass(frozen=True) +class UnsignedIntRegisterInputDescriptor(RegisterInputDescriptor): + """Input register representing an unsigned int.""" + + +@dataclass(frozen=True) +class StringRegisterInputDescriptor(RegisterInputDescriptor): + """Input register representing a string.""" + + number_of_bytes: int + + +@dataclass(frozen=True) +class RegisterBlockDescriptor: + """Based askoheat modbus block (range of registers) descriptor.""" + + starting_register: int + number_of_registers: int + + binary_sensors: list[AskoheatBinarySensorEntityDescription] = field( + default_factory=list + ) + sensors: list[AskoheatSensorEntityDescription] = field(default_factory=list) + switches: list[AskoheatSwitchEntityDescription] = field(default_factory=list) + number_inputs: list[AskoheatNumberEntityDescription] = field(default_factory=list) + + # TODO: add more type of inputs as soon as supported + # text_inputs: list[TextAttrKey, RegisterInputDescriptor] = field( + # default_factory=dict + # ) + # select_inputs: dict[SelectAttrKey, RegisterInputDescriptor] = field( + # default_factory=dict + # ) + # time_inputs: dict[TimeAttrKey, RegisterInputDescriptor] = field( + # default_factory=dict + # ) diff --git a/custom_components/askoheat/api_ema_desc.py b/custom_components/askoheat/api_ema_desc.py new file mode 100644 index 0000000..9b9127e --- /dev/null +++ b/custom_components/askoheat/api_ema_desc.py @@ -0,0 +1,243 @@ +"""Energymanager block Api descriptor classes.""" +# http://www.download.askoma.com/askofamily_plus/modbus/askoheat-modbus.html#EM_Block + +from __future__ import annotations + +from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.components.sensor.const import ( + SensorDeviceClass, + SensorStateClass, +) +from homeassistant.const import ( + UnitOfElectricPotential, + UnitOfPower, + UnitOfTemperature, +) + +from custom_components.askoheat.api_desc import ( + FlagRegisterInputDescriptor, + Float32RegisterInputDescriptor, + RegisterBlockDescriptor, + SignedIntRegisterInputDescriptor, + UnsignedIntRegisterInputDescriptor, +) +from custom_components.askoheat.const import ( + BinarySensorAttrKey, + SensorAttrKey, + SwitchAttrKey, +) +from custom_components.askoheat.model import ( + AskoheatBinarySensorEntityDescription, + AskoheatSensorEntityDescription, + AskoheatSwitchEntityDescription, +) + +EMA_REGISTER_BLOCK_DESCRIPTOR = RegisterBlockDescriptor( + starting_register=300, + number_of_registers=37, + binary_sensors=[ + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.HEATER1_ACTIVE, + icon="mdi:power-plug", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=0), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.HEATER2_ACTIVE, + icon="mdi:power-plug", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=1), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.HEATER3_ACTIVE, + icon="mdi:power-plug", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=2), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.PUMP_ACTIVE, + icon="mdi:pump", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=3), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.RELAY_BOARD_CONNECTED, + icon="mdi:connection", + device_class=BinarySensorDeviceClass.PROBLEM, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=4), + ), + # bit 5 ignored + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.HEAT_PUMP_REQUEST_ACTIVE, + icon="mdi:heat-pump", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=6), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.EMERGENCY_MODE_ACTIVE, + icon="mdi:car-emergency", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=7), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.LEGIONELLA_PROTECTION_ACTIVE, + icon="mdi:shield-sun", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=8), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.ANALOG_INPUT_ACTIVE, + icon="mdi:sine-wave", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=9), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.SETPOINT_ACTIVE, + icon="mdi:finance", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=10), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.LOAD_FEEDIN_ACTIVE, + icon="mdi:solar-power", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=11), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.AUTOHEATER_ACTIVE, + icon="mdi:water-boiler-auto", + inverted=True, + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=12), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.PUMP_RELAY_FOLLOW_UP_TIME_ACTIVE, + icon="mdi:water-boiler-auto", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=13), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.TEMP_LIMIT_REACHED, + icon="mdi:water-boiler-auto", + device_class=BinarySensorDeviceClass.RUNNING, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=14), + ), + AskoheatBinarySensorEntityDescription( + key=BinarySensorAttrKey.ERROR_OCCURED, + icon="mdi:water-thermometer", + device_class=BinarySensorDeviceClass.PROBLEM, + api_descriptor=FlagRegisterInputDescriptor(starting_register=16, bit=15), + ), + ], + switches=[ + AskoheatSwitchEntityDescription( + key=SwitchAttrKey.EMA_SET_HEATER_STEP_HEATER1, + icon="mdi:heat-wave", + entity_category=None, + api_descriptor=FlagRegisterInputDescriptor(starting_register=18, bit=0), + ), + AskoheatSwitchEntityDescription( + key=SwitchAttrKey.EMA_SET_HEATER_STEP_HEATER2, + icon="mdi:heat-wave", + entity_category=None, + api_descriptor=FlagRegisterInputDescriptor(starting_register=18, bit=1), + ), + AskoheatSwitchEntityDescription( + key=SwitchAttrKey.EMA_SET_HEATER_STEP_HEATER3, + icon="mdi:heat-wave", + entity_category=None, + api_descriptor=FlagRegisterInputDescriptor(starting_register=18, bit=2), + ), + ], + sensors=[ + AskoheatSensorEntityDescription( + key=SensorAttrKey.HEATER_LOAD, + icon="mdi:lightning-bolt", + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.POWER, + native_unit_of_measurement=UnitOfPower.WATT, + entity_category=None, + api_descriptor=UnsignedIntRegisterInputDescriptor(16), + ), + AskoheatSensorEntityDescription( + key=SensorAttrKey.LOAD_SETPOINT_VALUE, + icon="mdi:lightning-bolt", + native_precision=0, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.POWER, + native_unit_of_measurement=UnitOfPower.WATT, + entity_category=None, + api_descriptor=SignedIntRegisterInputDescriptor(19), + ), + AskoheatSensorEntityDescription( + key=SensorAttrKey.LOAD_FEEDIN_VALUE, + icon="mdi:solar-power", + native_precision=0, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.POWER, + native_unit_of_measurement=UnitOfPower.WATT, + entity_category=None, + api_descriptor=SignedIntRegisterInputDescriptor(20), + ), + AskoheatSensorEntityDescription( + key=SensorAttrKey.ANALOG_INPUT_VALUE, + icon="mdi:gauge", + native_precision=0, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.VOLTAGE, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + entity_category=None, + api_descriptor=Float32RegisterInputDescriptor(23), + ), + AskoheatSensorEntityDescription( + key=SensorAttrKey.INTERNAL_TEMPERATUR_SENSOR_VALUE, + icon="mdi:thermometer", + native_precision=1, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=None, + api_descriptor=Float32RegisterInputDescriptor(25), + ), + AskoheatSensorEntityDescription( + key=SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR1_VALUE, + icon="mdi:thermometer", + native_precision=1, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=None, + api_descriptor=Float32RegisterInputDescriptor(27), + ), + AskoheatSensorEntityDescription( + key=SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR2_VALUE, + icon="mdi:thermometer", + native_precision=1, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=None, + api_descriptor=Float32RegisterInputDescriptor(29), + ), + AskoheatSensorEntityDescription( + key=SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR3_VALUE, + icon="mdi:thermometer", + native_precision=1, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=None, + api_descriptor=Float32RegisterInputDescriptor(31), + ), + AskoheatSensorEntityDescription( + key=SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR4_VALUE, + icon="mdi:thermometer", + native_precision=1, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=None, + api_descriptor=Float32RegisterInputDescriptor(33), + ), + ], +) diff --git a/custom_components/askoheat/binary_sensor.py b/custom_components/askoheat/binary_sensor.py index ea88fe5..13f42ea 100644 --- a/custom_components/askoheat/binary_sensor.py +++ b/custom_components/askoheat/binary_sensor.py @@ -10,9 +10,8 @@ ) from homeassistant.core import callback -from custom_components.askoheat.binary_sensor_entities_ema import ( - EMA_BINARY_SENSOR_ENTITY_DESCRIPTIONS, -) +from custom_components.askoheat.api_ema_desc import EMA_REGISTER_BLOCK_DESCRIPTOR +from custom_components.askoheat.model import AskoheatBinarySensorEntityDescription from .entity import AskoheatEntity @@ -20,8 +19,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback - from custom_components.askoheat.model import AskoheatBinarySensorEntityDescription - from .coordinator import AskoheatDataUpdateCoordinator from .data import AskoheatConfigEntry @@ -37,11 +34,14 @@ async def async_setup_entry( coordinator=entry.runtime_data.ema_coordinator, entity_description=entity_description, ) - for entity_description in EMA_BINARY_SENSOR_ENTITY_DESCRIPTIONS + for entity_description in EMA_REGISTER_BLOCK_DESCRIPTOR.binary_sensors ) -class AskoheatBinarySensor(AskoheatEntity, BinarySensorEntity): +class AskoheatBinarySensor( + AskoheatEntity[AskoheatBinarySensorEntityDescription], + BinarySensorEntity, +): """askoheat binary_sensor class.""" entity_description: AskoheatBinarySensorEntityDescription diff --git a/custom_components/askoheat/binary_sensor_entities_ema.py b/custom_components/askoheat/binary_sensor_entities_ema.py deleted file mode 100644 index fc49acd..0000000 --- a/custom_components/askoheat/binary_sensor_entities_ema.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Predefined energy managementt askoheat sensors.""" - -from homeassistant.components.binary_sensor import BinarySensorDeviceClass - -from custom_components.askoheat.const import BinarySensorAttrKey -from custom_components.askoheat.model import AskoheatBinarySensorEntityDescription - -EMA_BINARY_SENSOR_ENTITY_DESCRIPTIONS = ( - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.HEATER1_ACTIVE, - icon="mdi:power-plug", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.HEATER2_ACTIVE, - icon="mdi:power-plug", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.HEATER3_ACTIVE, - icon="mdi:power-plug", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.PUMP_ACTIVE, - icon="mdi:pump", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.RELAY_BOARD_CONNECTED, - icon="mdi:connection", - device_class=BinarySensorDeviceClass.PROBLEM, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.HEAT_PUMP_REQUEST_ACTIVE, - icon="mdi:heat-pump", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.EMERGENCY_MODE_ACTIVE, - icon="mdi:car-emergency", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.LEGIONELLA_PROTECTION_ACTIVE, - icon="mdi:shield-sun", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.ANALOG_INPUT_ACTIVE, - icon="mdi:sine-wave", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.SETPOINT_ACTIVE, - icon="mdi:finance", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.LOAD_FEEDIN_ACTIVE, - icon="mdi:solar-power", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.AUTOHEATER_ACTIVE, - icon="mdi:water-boiler-auto", - inverted=True, - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.PUMP_RELAY_FOLLOW_UP_TIME_ACTIVE, - icon="mdi:water-boiler-auto", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.TEMP_LIMIT_REACHED, - icon="mdi:water-boiler-auto", - device_class=BinarySensorDeviceClass.RUNNING, - ), - AskoheatBinarySensorEntityDescription( - key=BinarySensorAttrKey.ERROR_OCCURED, - icon="mdi:water-thermometer", - device_class=BinarySensorDeviceClass.PROBLEM, - ), -) diff --git a/custom_components/askoheat/coordinator.py b/custom_components/askoheat/coordinator.py index 0506fb6..561128b 100644 --- a/custom_components/askoheat/coordinator.py +++ b/custom_components/askoheat/coordinator.py @@ -101,8 +101,8 @@ def _map_data_block_to_dict(data: AskoheatDataBlock) -> dict[str, Any]: {f"number.{k}": data.number_inputs[k] for k in data.number_inputs} ) - if data.text_intputs: - result.update({f"text.{k}": data.text_intputs[k] for k in data.text_intputs}) + if data.text_inputs: + result.update({f"text.{k}": data.text_inputs[k] for k in data.text_inputs}) if data.select_inputs: result.update( diff --git a/custom_components/askoheat/data.py b/custom_components/askoheat/data.py index 04ddb03..edca6a3 100644 --- a/custom_components/askoheat/data.py +++ b/custom_components/askoheat/data.py @@ -50,7 +50,7 @@ class AskoheatDataBlock: binary_sensors: dict[BinarySensorAttrKey, bool] | None = None sensors: dict[SensorAttrKey, object] | None = None switches: dict[SwitchAttrKey, bool] | None = None - text_intputs: dict[TextAttrKey, str] | None = None + text_inputs: dict[TextAttrKey, str] | None = None select_inputs: dict[SelectAttrKey, ReprEnum] | None = None number_inputs: dict[NumberAttrKey, number] | None = None time_inputs: dict[TimeAttrKey, time | None] | None = None diff --git a/custom_components/askoheat/entity.py b/custom_components/askoheat/entity.py index 599523e..1c778e6 100644 --- a/custom_components/askoheat/entity.py +++ b/custom_components/askoheat/entity.py @@ -2,29 +2,30 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TypeVar from homeassistant.core import callback from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity +from custom_components.askoheat.model import AskoheatEntityDescription + from .const import ATTRIBUTION from .coordinator import AskoheatDataUpdateCoordinator -if TYPE_CHECKING: - from custom_components.askoheat.model import AskoheatEntityDescription +E = TypeVar("E", bound=AskoheatEntityDescription) -class AskoheatEntity(CoordinatorEntity[AskoheatDataUpdateCoordinator]): +class AskoheatEntity[E](CoordinatorEntity[AskoheatDataUpdateCoordinator]): """AskoheatEntity class.""" _attr_attribution = ATTRIBUTION - entity_description: AskoheatEntityDescription + entity_description: E def __init__( self, coordinator: AskoheatDataUpdateCoordinator, - entity_description: AskoheatEntityDescription, + entity_description: E, ) -> None: """Initialize.""" super().__init__(coordinator) diff --git a/custom_components/askoheat/model.py b/custom_components/askoheat/model.py index d451e84..92e2a76 100644 --- a/custom_components/askoheat/model.py +++ b/custom_components/askoheat/model.py @@ -2,7 +2,7 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import StrEnum from functools import cached_property from typing import TYPE_CHECKING, TypeVar @@ -14,6 +14,14 @@ from homeassistant.const import Platform from homeassistant.helpers.entity import EntityDescription +from custom_components.askoheat.api_desc import ( + ByteRegisterInputDescriptor, + FlagRegisterInputDescriptor, + Float32RegisterInputDescriptor, + RegisterInputDescriptor, + SignedIntRegisterInputDescriptor, + UnsignedIntRegisterInputDescriptor, +) from custom_components.askoheat.const import ( DOMAIN, BinarySensorAttrKey, @@ -28,19 +36,22 @@ K = TypeVar("K", bound=StrEnum) +A = TypeVar("A", bound=RegisterInputDescriptor) @dataclass(frozen=True) -class AskoheatEntityDescription[K](EntityDescription): +class AskoheatEntityDescription[K, A](EntityDescription): """Class describing base askoheat entity.""" key: K + api_descriptor: A | None = None icon_by_state: dict[date | datetime | Decimal, str] | None = None @dataclass(frozen=True) class AskoheatBinarySensorEntityDescription( - AskoheatEntityDescription[BinarySensorAttrKey], BinarySensorEntityDescription + AskoheatEntityDescription[BinarySensorAttrKey, FlagRegisterInputDescriptor], + BinarySensorEntityDescription, ): """Class describing Askoheat binary sensor entities.""" @@ -60,7 +71,7 @@ def data_key(self) -> str: @dataclass(frozen=True) class AskoheatSwitchEntityDescription( - AskoheatEntityDescription[SwitchAttrKey], + AskoheatEntityDescription[SwitchAttrKey, FlagRegisterInputDescriptor], SwitchEntityDescription, ): """Class describing Askoheat switch entities.""" @@ -81,7 +92,13 @@ def data_key(self) -> str: @dataclass(frozen=True) class AskoheatSensorEntityDescription( - AskoheatEntityDescription[SensorAttrKey], + AskoheatEntityDescription[ + SensorAttrKey, + ByteRegisterInputDescriptor + | UnsignedIntRegisterInputDescriptor + | SignedIntRegisterInputDescriptor + | Float32RegisterInputDescriptor, + ], SensorEntityDescription, ): """Class describing Askoheat sensor entities.""" @@ -100,7 +117,13 @@ def data_key(self) -> str: @dataclass(frozen=True) class AskoheatNumberEntityDescription( - AskoheatEntityDescription[NumberAttrKey], + AskoheatEntityDescription[ + NumberAttrKey, + ByteRegisterInputDescriptor + | UnsignedIntRegisterInputDescriptor + | SignedIntRegisterInputDescriptor + | Float32RegisterInputDescriptor, + ], NumberEntityDescription, ): """Class describing Askoheat number entities.""" diff --git a/custom_components/askoheat/number.py b/custom_components/askoheat/number.py index 0a0d6e6..b5cd3e5 100644 --- a/custom_components/askoheat/number.py +++ b/custom_components/askoheat/number.py @@ -7,8 +7,9 @@ from homeassistant.components.number import ENTITY_ID_FORMAT, NumberEntity from homeassistant.core import callback -from custom_components.askoheat.number_entities_config import ( - CONF_NUMBER_ENTITY_DESCRIPTIONS, +from custom_components.askoheat.api_conf_desc import CONF_REGISTER_BLOCK_DESCRIPTOR +from custom_components.askoheat.model import ( + AskoheatNumberEntityDescription, ) from .entity import AskoheatEntity @@ -17,10 +18,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback - from custom_components.askoheat.model import ( - AskoheatNumberEntityDescription, - ) - from .coordinator import AskoheatDataUpdateCoordinator from .data import AskoheatConfigEntry @@ -36,11 +33,11 @@ async def async_setup_entry( coordinator=entry.runtime_data.config_coordinator, entity_description=entity_description, ) - for entity_description in CONF_NUMBER_ENTITY_DESCRIPTIONS + for entity_description in CONF_REGISTER_BLOCK_DESCRIPTOR.number_inputs ) -class AskoHeatNumber(AskoheatEntity, NumberEntity): +class AskoHeatNumber(AskoheatEntity[AskoheatNumberEntityDescription], NumberEntity): """askoheat number class.""" entity_description: AskoheatNumberEntityDescription diff --git a/custom_components/askoheat/number_entities_config.py b/custom_components/askoheat/number_entities_config.py deleted file mode 100644 index 49c9137..0000000 --- a/custom_components/askoheat/number_entities_config.py +++ /dev/null @@ -1,430 +0,0 @@ -"""Predefined configuration askoheat number entities.""" - -from homeassistant.components.number import NumberMode -from homeassistant.const import ( - EntityCategory, - UnitOfElectricPotential, - UnitOfPower, - UnitOfTemperature, - UnitOfTime, - UnitOfVolume, -) - -from custom_components.askoheat.const import NumberAttrKey -from custom_components.askoheat.model import ( - AskoheatNumberEntityDescription, -) - -CONF_NUMBER_ENTITY_DESCRIPTIONS = ( - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_RELAY_SEC_COUNT_SECONDS, - native_min_value=0, - native_max_value=16, - native_default_value=5, - native_unit_of_measurement=UnitOfTime.SECONDS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:timelapse", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_PUMP_SEC_COUNT_SECONDS, - native_min_value=0, - native_max_value=240, - native_default_value=30, - native_unit_of_measurement=UnitOfTime.SECONDS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:timelapse", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_AUTO_HEATER_OFF_MINUTES, - native_min_value=2, - native_max_value=10080, - native_unit_of_measurement=UnitOfTime.MINUTES, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:timer-outline", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_CASCADE_PRIORIZATION, - native_min_value=0, - native_max_value=255, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:priority-high", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_HEATBUFFER_VOLUME, - native_min_value=0, - native_max_value=1000, - native_unit_of_measurement=UnitOfVolume.LITERS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:waves", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_LEGIO_PROTECTION_TEMPERATURE, - native_min_value=50, - native_max_value=65, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:water-thermometer", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_LEGIO_PROTECTION_HEATUP_MINUTES, - native_min_value=0, - native_max_value=1440, - native_default_value=240, - native_unit_of_measurement=UnitOfTime.MINUTES, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:timelapse", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_NUMBER_OF_HOUSEHOLD_MEMBERS, - native_min_value=1, - native_max_value=255, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:human-male-female-child", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_LOAD_FEEDIN_BASIC_ENERGY_LEVEL, - native_min_value=0, - native_max_value=10000, - native_unit_of_measurement=UnitOfPower.WATT, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:lightning-bolt", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_TIMEZONE_OFFSET, - native_min_value=-12, - native_max_value=12, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:map-clock", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_LOAD_FEEDIN_DELAY_SECONDS, - native_min_value=0, - native_max_value=120, - native_unit_of_measurement=UnitOfTime.SECONDS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:timer-sand", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_RTU_SLAVE_ID, - native_min_value=0, - native_max_value=240, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:identifier", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_HYSTERESIS, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:slope-downhill", - ), - # Analog input 0 - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_0_THRESHOLD, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:slope-uphill", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_0_THRESHOLD_STEP, - native_min_value=0, - native_max_value=7, - native_step=1, - native_default_value=7, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:gauge", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_0_THRESHOLD_TEMPERATURE, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer", - ), - # Analog input 1 - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_1_THRESHOLD, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:slope-uphill", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_1_THRESHOLD_STEP, - native_min_value=0, - native_max_value=7, - native_step=1, - native_default_value=7, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:stairs", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_1_THRESHOLD_TEMPERATURE, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer", - ), - # Analog input 2 - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_2_THRESHOLD, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:slope-uphill", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_2_THRESHOLD_STEP, - native_min_value=0, - native_max_value=7, - native_step=1, - native_default_value=7, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:stairs", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_2_THRESHOLD_TEMPERATURE, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer", - ), - # Analog input 3 - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_3_THRESHOLD, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:slope-uphill", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_3_THRESHOLD_STEP, - native_min_value=0, - native_max_value=7, - native_step=1, - native_default_value=7, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:stairs", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_3_THRESHOLD_TEMPERATURE, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer", - ), - # Analog input 4 - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_4_THRESHOLD, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:slope-uphill", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_4_THRESHOLD_STEP, - native_min_value=0, - native_max_value=7, - native_step=1, - native_default_value=7, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:stairs", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_4_THRESHOLD_TEMPERATURE, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer", - ), - # Analog input 5 - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_5_THRESHOLD, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:slope-uphill", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_5_THRESHOLD_STEP, - native_min_value=0, - native_max_value=7, - native_step=1, - native_default_value=7, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:stairs", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_5_THRESHOLD_TEMPERATURE, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer", - ), - # Analog input 6 - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_6_THRESHOLD, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:slope-uphill", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_6_THRESHOLD_STEP, - native_min_value=0, - native_max_value=7, - native_step=1, - native_default_value=7, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:stairs", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_6_THRESHOLD_TEMPERATURE, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer", - ), - # Analog input 7 - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_7_THRESHOLD, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:slope-uphill", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_7_THRESHOLD_STEP, - native_min_value=0, - native_max_value=7, - native_step=1, - native_default_value=7, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:stairs", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_ANALOG_INPUT_7_THRESHOLD_TEMPERATURE, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_HEAT_PUMP_REQUEST_OFF_STEP, - native_min_value=0, - native_max_value=7, - native_step=1, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:stairs", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_HEAT_PUMP_REQUEST_ON_STEP, - native_min_value=0, - native_max_value=7, - native_step=1, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:stairs", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_EMERGENCY_MODE_ON_STOP, - native_min_value=0, - native_max_value=7, - native_step=1, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.SLIDER, - icon="mdi:stairs", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_TEMPERATURE_HYSTERESIS, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_MINIMAL_TEMPERATURE, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer-minus", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_SET_HEATER_STEP_TEMPERATURE_LIMIT, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer-chevron-up", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_LOAD_FEEDIN_OR_SETPOINT_TEMPERATURE_LIMIT, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer-chevron-up", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_LOW_TARIFF_TEMPERATURE_LIMIT, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer-chevron-up", - ), - AskoheatNumberEntityDescription( - key=NumberAttrKey.CON_HEATPUMP_REQUEST_TEMPERATURE_LIMIT, - native_min_value=0, - native_max_value=95, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=EntityCategory.CONFIG, - mode=NumberMode.BOX, - icon="mdi:thermometer-chevron-up", - ), -) diff --git a/custom_components/askoheat/sensor.py b/custom_components/askoheat/sensor.py index b3e5ce3..9422159 100644 --- a/custom_components/askoheat/sensor.py +++ b/custom_components/askoheat/sensor.py @@ -8,9 +8,8 @@ from homeassistant.components.sensor import ENTITY_ID_FORMAT, SensorEntity from homeassistant.core import callback -from custom_components.askoheat.sensor_entities_ema import ( - EMA_SENSOR_ENTITY_DESCRIPTIONS, -) +from custom_components.askoheat.api_ema_desc import EMA_REGISTER_BLOCK_DESCRIPTOR +from custom_components.askoheat.model import AskoheatSensorEntityDescription from .entity import AskoheatEntity @@ -18,8 +17,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback - from custom_components.askoheat.model import AskoheatSensorEntityDescription - from .coordinator import AskoheatDataUpdateCoordinator from .data import AskoheatConfigEntry @@ -35,11 +32,11 @@ async def async_setup_entry( coordinator=entry.runtime_data.ema_coordinator, entity_description=entity_description, ) - for entity_description in EMA_SENSOR_ENTITY_DESCRIPTIONS + for entity_description in EMA_REGISTER_BLOCK_DESCRIPTOR.sensors ) -class AskoheatSensor(AskoheatEntity, SensorEntity): +class AskoheatSensor(AskoheatEntity[AskoheatSensorEntityDescription], SensorEntity): """askoheat Sensor class.""" entity_description: AskoheatSensorEntityDescription diff --git a/custom_components/askoheat/sensor_entities_ema.py b/custom_components/askoheat/sensor_entities_ema.py deleted file mode 100644 index 4f067aa..0000000 --- a/custom_components/askoheat/sensor_entities_ema.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Predefined energy managementt askoheat sensors.""" - -from homeassistant.components.sensor.const import ( - SensorDeviceClass, - SensorStateClass, -) -from homeassistant.const import ( - UnitOfElectricPotential, - UnitOfPower, - UnitOfTemperature, -) - -from custom_components.askoheat.const import SensorAttrKey -from custom_components.askoheat.model import AskoheatSensorEntityDescription - -EMA_SENSOR_ENTITY_DESCRIPTIONS = ( - AskoheatSensorEntityDescription( - key=SensorAttrKey.ANALOG_INPUT_VALUE, - icon="mdi:gauge", - native_precision=0, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.VOLTAGE, - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - entity_category=None, - ), - AskoheatSensorEntityDescription( - key=SensorAttrKey.HEATER_LOAD, - icon="mdi:lightning-bolt", - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.POWER, - native_unit_of_measurement=UnitOfPower.WATT, - entity_category=None, - ), - AskoheatSensorEntityDescription( - key=SensorAttrKey.LOAD_FEEDIN_VALUE, - icon="mdi:solar-power", - native_precision=0, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.POWER, - native_unit_of_measurement=UnitOfPower.WATT, - entity_category=None, - ), - AskoheatSensorEntityDescription( - key=SensorAttrKey.LOAD_SETPOINT_VALUE, - icon="mdi:lightning-bolt", - native_precision=0, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.POWER, - native_unit_of_measurement=UnitOfPower.WATT, - entity_category=None, - ), - AskoheatSensorEntityDescription( - key=SensorAttrKey.INTERNAL_TEMPERATUR_SENSOR_VALUE, - icon="mdi:thermometer", - native_precision=1, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.TEMPERATURE, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=None, - ), - AskoheatSensorEntityDescription( - key=SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR1_VALUE, - icon="mdi:thermometer", - native_precision=1, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.TEMPERATURE, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=None, - ), - AskoheatSensorEntityDescription( - key=SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR2_VALUE, - icon="mdi:thermometer", - native_precision=1, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.TEMPERATURE, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=None, - ), - AskoheatSensorEntityDescription( - key=SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR3_VALUE, - icon="mdi:thermometer", - native_precision=1, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.TEMPERATURE, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=None, - ), - AskoheatSensorEntityDescription( - key=SensorAttrKey.EXTERNAL_TEMPERATUR_SENSOR4_VALUE, - icon="mdi:thermometer", - native_precision=1, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.TEMPERATURE, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - entity_category=None, - ), -) diff --git a/custom_components/askoheat/switch.py b/custom_components/askoheat/switch.py index 9d63f6c..4a1230b 100644 --- a/custom_components/askoheat/switch.py +++ b/custom_components/askoheat/switch.py @@ -7,9 +7,8 @@ from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity from homeassistant.core import callback -from custom_components.askoheat.switch_entities_ema import ( - EMA_SWITCH_ENTITY_DESCRIPTIONS, -) +from custom_components.askoheat.api_ema_desc import EMA_REGISTER_BLOCK_DESCRIPTOR +from custom_components.askoheat.model import AskoheatSwitchEntityDescription from .entity import AskoheatEntity @@ -17,8 +16,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback - from custom_components.askoheat.model import AskoheatSwitchEntityDescription - from .coordinator import AskoheatDataUpdateCoordinator from .data import AskoheatConfigEntry @@ -34,15 +31,13 @@ async def async_setup_entry( coordinator=entry.runtime_data.ema_coordinator, entity_description=entity_description, ) - for entity_description in EMA_SWITCH_ENTITY_DESCRIPTIONS + for entity_description in EMA_REGISTER_BLOCK_DESCRIPTOR.switches ) -class AskoHeatSwitch(AskoheatEntity, SwitchEntity): +class AskoHeatSwitch(AskoheatEntity[AskoheatSwitchEntityDescription], SwitchEntity): """askoheat switch class.""" - entity_description: AskoheatSwitchEntityDescription - def __init__( self, coordinator: AskoheatDataUpdateCoordinator, @@ -77,10 +72,26 @@ def _handle_coordinator_update(self) -> None: async def async_turn_on(self, **_: Any) -> None: """Turn on the switch.""" - # await self.coordinator.config_entry.runtime_data.client.async_set_title("bar") - # await self.coordinator.async_request_refresh() + await self._set_state(self.entity_description.on_state) async def async_turn_off(self, **_: Any) -> None: """Turn off the switch.""" - # await self.coordinator.config_entry.runtime_data.client.async_set_title("foo") - # await self.coordinator.async_request_refresh() + await self._set_state(self.entity_description.off_state) + + async def _set_state(self, state: str | bool) -> None: + """.""" + # data = await self.coordinator.async_write( + # self.entity_description.data_key.value.split(".")[1], state + # ) + # value = get_sensor_data(data, self.entity_description.key.value) + # if ( + # self.entity_description.on_state is True + # or self.entity_description.on_state is False + # ): + # value = bool(value) + # self._attr_is_on = ( + # value != self.entity_description.on_state + # if self.entity_description.inverted + # else value == self.entity_description.on_state + # ) + # self._handle_coordinator_update() diff --git a/custom_components/askoheat/switch_entities_ema.py b/custom_components/askoheat/switch_entities_ema.py deleted file mode 100644 index d15abb5..0000000 --- a/custom_components/askoheat/switch_entities_ema.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Predefined energy manager switches.""" - -from custom_components.askoheat.const import SwitchAttrKey -from custom_components.askoheat.model import AskoheatSwitchEntityDescription - -EMA_SWITCH_ENTITY_DESCRIPTIONS = ( - AskoheatSwitchEntityDescription( - key=SwitchAttrKey.EMA_SET_HEATER_STEP_HEATER1, - icon="mdi:heat-wave", - entity_category=None, - ), - AskoheatSwitchEntityDescription( - key=SwitchAttrKey.EMA_SET_HEATER_STEP_HEATER2, - icon="mdi:heat-wave", - entity_category=None, - ), - AskoheatSwitchEntityDescription( - key=SwitchAttrKey.EMA_SET_HEATER_STEP_HEATER3, - icon="mdi:heat-wave", - entity_category=None, - ), -)