diff --git a/LICENSE b/LICENSE index feba32d..5730fad 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 MadOne +Copyright (c) 2024 OStrama Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f1dafec..7df5e97 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ -# THIS INTEGRATION IS NOT LONGER DEVELOPED. -# There is a far superior approach at: -# https://github.com/OStrama/weishaupt_modbus - - +This is a fork from MadOne: https://github.com/MadOne/weishaupt_modbus/ +I started to build a structure that finally will allow loading of the modbus structure from a file. +As a first step, all modbus parameters will be concentrated in the file hpconst.py as a set of object lists. +This allows generic setup of all entities and a more easy completion of messages and entity behavior +#### not yet ready #### +For production, please use https://github.com/MadOne/weishaupt_modbus/ for now. +When it's ready, I'll distribute this fork via HACS # Weishaupt_modbus @@ -15,7 +17,7 @@ This is how it might look: ## Installation -### Install through HACS +### Install through HACS - Not working yet @@ -23,7 +25,7 @@ This is how it might look: Add this repository to HACS. * In the HACS GUI, select "Custom repositories" -* Enter the following repository URL: https://github.com/MadOne/weishaupt_modbus/releases +* Enter the following repository URL: https://github.com/OStrama/weishaupt_modbus/releases * Category: Integration * After adding the integration, restart Home Assistant. * Now under Configuration -> Integrations, "Weishaupt Modbus Integration" should be available. @@ -43,7 +45,7 @@ custom_components │ ├── ... │ ├── ... │ ├── ... -│ └── wp.py +│ └── wp.py ``` ## Configuration @@ -51,7 +53,7 @@ custom_components Just enter the IP of your Weishaupt heatpump. Port should be ok at default unless you changed it in the Heatpump configuration. -You have to enable modbus in your heatpump settings. +You have to enable modbus in your heatpump settings. ## Setting up the HeatPump diff --git a/custom_components/weishaupt_modbus/__init__.py b/custom_components/weishaupt_modbus/__init__.py index 652ef35..91f2242 100644 --- a/custom_components/weishaupt_modbus/__init__.py +++ b/custom_components/weishaupt_modbus/__init__.py @@ -1,22 +1,18 @@ -"""Weishaupt Modbus Integration.""" - from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import DOMAIN +from .const import CONST PLATFORMS: list[str] = [ "number", "select", "sensor", - "switch", +# "switch", ] - # Return boolean to indicate that initialization was successful. # return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up Hello World from a config entry.""" # Store an instance of the "connecting" class that does the work of speaking # with your actual devices. # hass.data.setdefault(DOMAIN, {})[entry.entry_id] = hub.Hub(hass, entry.data["host"]) @@ -26,14 +22,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Unload a config entry.""" # This is called when an entry/configured device is to be removed. The class # needs to unload itself, and remove callbacks. See the classes for further # details unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) + hass.data[CONST.DOMAIN].pop(entry.entry_id) return unload_ok diff --git a/custom_components/weishaupt_modbus/config_flow.py b/custom_components/weishaupt_modbus/config_flow.py index 880a486..eed0946 100644 --- a/custom_components/weishaupt_modbus/config_flow.py +++ b/custom_components/weishaupt_modbus/config_flow.py @@ -1,28 +1,19 @@ -"""Weishaupt Modbus Integration.""" - from typing import Any - import voluptuous as vol - from homeassistant import config_entries, exceptions from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv # from . import wp -from .const import DOMAIN +from .const import CONST # DATA_SCHEMA = vol.Schema({("host"): str, ("port"): cv.port}) DATA_SCHEMA = vol.Schema( {vol.Required(CONF_HOST): str, vol.Optional(CONF_PORT, default="502"): cv.port} ) - async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]: - """Validate the user input allows us to connect. - - Data has the keys from DATA_SCHEMA with values provided by the user. - """ # Validate the data can be used to set up a connection. # This is a simple example to show an error in the UI for a short hostname @@ -31,10 +22,6 @@ async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]: if len(data["host"]) < 3: raise InvalidHost - # whp = wp.heat_pump("10.10.1.225", 502) - # if not whp: - # raise ConnectionFailed - # If your PyPI package is not built with async, pass your methods # to the executor: # await hass.async_add_executor_job( @@ -53,9 +40,7 @@ async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]: return {"title": data["host"]} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for Hello World.""" - +class ConfigFlow(config_entries.ConfigFlow, domain=CONST.DOMAIN): VERSION = 1 # Pick one of the available connection classes in homeassistant/config_entries.py # This tells HA if it should be asking for updates, or it'll be notified of updates @@ -64,7 +49,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH async def async_step_user(self, user_input=None): - """Handle the initial step.""" # This goes through the steps to take the user through the setup process. # Using this it is possible to update the UI and prompt for additional # information. This example provides a single form (built from `DATA_SCHEMA`), @@ -81,15 +65,13 @@ async def async_step_user(self, user_input=None): except Exception: # noqa: BLE001 errors["base"] = "unknown" - # If there is no user input or there were errors, show the form again, including any errors that were found with the input. +# If there is no user input or there were errors, show the form again, including any errors that were found with the input. return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - class InvalidHost(exceptions.HomeAssistantError): """Error to indicate there is an invalid hostname.""" - class ConnectionFailed(exceptions.HomeAssistantError): """Error to indicate there is an invalid hostname.""" diff --git a/custom_components/weishaupt_modbus/const.py b/custom_components/weishaupt_modbus/const.py index 8e32865..d8b787f 100644 --- a/custom_components/weishaupt_modbus/const.py +++ b/custom_components/weishaupt_modbus/const.py @@ -1,8 +1,36 @@ -"""Constants.""" - +from dataclasses import dataclass from datetime import timedelta +from homeassistant.const import UnitOfEnergy, UnitOfTemperature, UnitOfTime, UnitOfVolumeFlowRate, PERCENTAGE + +@dataclass(frozen=True) +class MainConstants: + DOMAIN = "weishaupt_wbb" + SCAN_INTERVAL = timedelta(minutes=1) + UNIQUE_ID = "unique_id" + APPID = 100 + +CONST = MainConstants() + +@dataclass(frozen=True) +class FormatConstants: + TEMPERATUR = UnitOfTemperature.CELSIUS + ENERGY = UnitOfEnergy.KILO_WATT_HOUR + PERCENTAGE = PERCENTAGE + NUMBER = "" + STATUS = "Status" + VOLUMENSTROM = UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR + KENNLINIE = "Stg." + TIME_MIN = UnitOfTime.MINUTES + TIME_H = UnitOfTime.HOURS + +FORMATS = FormatConstants() + +@dataclass(frozen=True) +class TypeConstants: + SENSOR = "Sensor" + SELECT = "Select" + NUMBER = "Number" + NUMBER_RO = "Number_RO" + +TYPES = TypeConstants() -DOMAIN = "weishaupt_modbus" -SCAN_INTERVAL = timedelta(minutes=1) -UNIQUE_ID = "unique_id" -APPID = 100 diff --git a/custom_components/weishaupt_modbus/entities - Kopie.py b/custom_components/weishaupt_modbus/entities - Kopie.py new file mode 100644 index 0000000..88a12df --- /dev/null +++ b/custom_components/weishaupt_modbus/entities - Kopie.py @@ -0,0 +1,196 @@ +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PORT +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity, SensorStateClass +from homeassistant.components.select import SelectEntity +from homeassistant.components.number import NumberEntity +from homeassistant.const import UnitOfEnergy, UnitOfTemperature +from homeassistant.helpers.device_registry import DeviceInfo +from .const import CONST, FORMATS, TYPES +from .modbusobject import ModbusObject + +def BuildEntityList(entries, config_entry, modbusitems, type): + # this builds a list of entities that can be used as parameter by async_setup_entry() + # type of list is defined by the ModbusItem's type flag + # so the app only holds one list of entities that is build from a list of ModbusItem + # stored in hpconst.py so far, will be provided by an external file in future + for index, item in enumerate(modbusitems): + if item.type == type: + match type: + # here the entities are created with the parameters provided by the ModbusItem object + case TYPES.SENSOR: + entries.append(MySensorEntity(config_entry, modbusitems[index])) + case TYPES.SELECT: + entries.append(MySelectEntity(config_entry, modbusitems[index])) + case TYPES.NUMBER: + entries.append(MyNumberEntity(config_entry, modbusitems[index])) + + return entries + +class MyEntity(): + # The base class for entities that hold general parameters + _config_entry = None + _modbus_item = None + _attr_name = "" + _attr_unique_id = "" + _attr_should_poll = True + _dev_device = "" + + def __init__(self, config_entry, modbus_item) -> None: + self._config_entry = config_entry + self._modbus_item = modbus_item + self._attr_name = self._modbus_item.name + self._attr_unique_id = CONST.DOMAIN + self._attr_name + self._dev_device = self._modbus_item.device + + if self._modbus_item._format != FORMATS.STATUS: + self._attr_native_unit_of_measurement = self._modbus_item._format + self._attr_state_class = SensorStateClass.MEASUREMENT + + if self._modbus_item.resultlist != None: + self._attr_native_min_value = self._modbus_item.getNumberFromText("min") + self._attr_native_max_value = self._modbus_item.getNumberFromText("max") + self._attr_native_step = self._modbus_item.getNumberFromText("step") + + if self._modbus_item._format == FORMATS.TEMPERATUR: + self._attr_device_class = SensorDeviceClass.TEMPERATURE + + def calcTemperature(self,val: float): + if val == None: + return None + if val == -32768: + return -1 + if val == -32767: + return -2 + if val == 32768: + return None + return val / 10.0 + + def calcPercentage(self,val: float): + if val == 65535: + return None + return val + + @property + def translateVal(self): + # reads an translates a value from the modbua + mbo = ModbusObject(self._config_entry, self._modbus_item) + val = mbo.value + match self._modbus_item.format: + case FORMATS.TEMPERATUR: + return self.calcTemperature(val) + case FORMATS.PERCENTAGE: + return self.calcPercentage(val) + case FORMATS.STATUS: + return self._modbus_item.getTextFromNumber(val) + case FORMATS.KENNLINIE: + return val / 100.0 + case _: + return val + + @translateVal.setter + def translateVal(self,value): + # translates and writes a value to the modbus + mbo = ModbusObject(self._config_entry, self._modbus_item) + val = None + match self._modbus_item.format: + # logically, this belongs to the ModbusItem, but doing it here + # maybe adding a translate function in MyEntity? + # currently it saves a lot of code lines ;-) + case FORMATS.TEMPERATUR: + val = value * 10 + case FORMATS.KENNLINIE: + val = value * 100 + case FORMATS.STATUS: + val = self._modbus_item.getNumberFromText(value) + case _: + val = value + mbo.value = val + + def my_device_info(self) -> DeviceInfo: + # helper to build the device info + return { + "identifiers": {(CONST.DOMAIN, self._dev_device)}, + "name": self._dev_device, + "sw_version": "Device_SW_Version", + "model": "Device_model", + "manufacturer": "Weishaupt", + } + +class MySensorEntity(SensorEntity, MyEntity): + # class that represents a sensor entity derived from Sensorentity + # and decorated with general parameters from MyEntity + _attr_native_unit_of_measurement = None + _attr_device_class = None + _attr_state_class = None + + def __init__(self, config_entry, modbus_item) -> None: + MyEntity.__init__(self, config_entry, modbus_item) + + async def async_update(self) -> None: + # the synching is done by the ModbusObject of the entity + self._attr_native_value = self.translateVal + + @property + def device_info(self) -> DeviceInfo: + return MyEntity.my_device_info(self) + +class MyNumberEntity(NumberEntity, MyEntity): + # class that represents a sensor entity derived from Sensorentity + # and decorated with general parameters from MyEntity + _attr_native_unit_of_measurement = None + _attr_device_class = None + _attr_state_class = None + _attr_native_min_value = 10 + _attr_native_max_value = 60 + + + def __init__(self, config_entry, modbus_item) -> None: + MyEntity.__init__(self, config_entry, modbus_item) + + if self._modbus_item.resultlist != None: + self._attr_native_min_value = self._modbus_item.getNumberFromText("min") + self._attr_native_max_value = self._modbus_item.getNumberFromText("max") + self._attr_native_step = self._modbus_item.getNumberFromText("step") + + async def async_set_native_value(self, value: float) -> None: + self.translateVal = value + self._attr_native_value = self.translateVal + self.async_write_ha_state() + + async def async_update(self) -> None: + # the synching is done by the ModbusObject of the entity + self._attr_native_value = self.translateVal + + @property + def device_info(self) -> DeviceInfo: + return MyEntity.my_device_info(self) + + +class MySelectEntity(SelectEntity, MyEntity): + # class that represents a sensor entity derived from Sensorentity + # and decorated with general parameters from MyEntity + options = [] + _attr_current_option = "FEHLER" + + def __init__(self, config_entry, modbus_item) -> None: + MyEntity.__init__(self, config_entry, modbus_item) + self.async_internal_will_remove_from_hass_port = self._config_entry.data[CONF_PORT] + # option list build from the status list of the ModbusItem + self.options = [] + for index, item in enumerate(self._modbus_item._resultlist): + self.options.append(item.text) + + async def async_select_option(self, option: str) -> None: + # the synching is done by the ModbusObject of the entity + self.translateVal = option + self._attr_current_option = self.translateVal + self.async_write_ha_state() + + async def async_update(self) -> None: + # the synching is done by the ModbusObject of the entity + # await self.coordinator.async_request_refresh() + self._attr_current_option = self.translateVal + + @property + def device_info(self) -> DeviceInfo: + return MyEntity.my_device_info(self) diff --git a/custom_components/weishaupt_modbus/entities.py b/custom_components/weishaupt_modbus/entities.py new file mode 100644 index 0000000..30852ee --- /dev/null +++ b/custom_components/weishaupt_modbus/entities.py @@ -0,0 +1,194 @@ +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PORT +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity, SensorStateClass +from homeassistant.components.select import SelectEntity +from homeassistant.components.number import NumberEntity +from homeassistant.const import UnitOfEnergy, UnitOfTemperature +from homeassistant.helpers.device_registry import DeviceInfo +from .const import CONST, FORMATS, TYPES +from .modbusobject import ModbusObject + +def BuildEntityList(entries, config_entry, modbusitems, type): + # this builds a list of entities that can be used as parameter by async_setup_entry() + # type of list is defined by the ModbusItem's type flag + # so the app only holds one list of entities that is build from a list of ModbusItem + # stored in hpconst.py so far, will be provided by an external file in future + for index, item in enumerate(modbusitems): + if item.type == type: + match type: + # here the entities are created with the parameters provided by the ModbusItem object + case TYPES.SENSOR | TYPES.NUMBER_RO: + entries.append(MySensorEntity(config_entry, modbusitems[index])) + case TYPES.SELECT: + entries.append(MySelectEntity(config_entry, modbusitems[index])) + case TYPES.NUMBER: + entries.append(MyNumberEntity(config_entry, modbusitems[index])) + + return entries + +class MyEntity(): + # The base class for entities that hold general parameters + _config_entry = None + _modbus_item = None + _divider = 1 + _attr_name = "" + _attr_unique_id = "" + _attr_should_poll = True + _dev_device = "" + + def __init__(self, config_entry, modbus_item) -> None: + self._config_entry = config_entry + self._modbus_item = modbus_item + self._attr_name = self._modbus_item.name + self._attr_unique_id = CONST.DOMAIN + self._attr_name + self._dev_device = self._modbus_item.device + + if self._modbus_item._format != FORMATS.STATUS: + self._attr_native_unit_of_measurement = self._modbus_item._format + + if self._modbus_item._format == FORMATS.ENERGY: + self._attr_state_class = SensorStateClass.TOTAL_INCREASING + if self._modbus_item._format == FORMATS.TEMPERATUR: + self._attr_state_class = SensorStateClass.MEASUREMENT + + if self._modbus_item.resultlist != None: + self._attr_native_min_value = self._modbus_item.getNumberFromText("min") + self._attr_native_max_value = self._modbus_item.getNumberFromText("max") + self._attr_native_step = self._modbus_item.getNumberFromText("step") + self._divider = self._modbus_item.getNumberFromText("divider") + self._attr_device_class = self._modbus_item.getTextFromNumber(-1) + + def calcTemperature(self,val: float): + if val == None: + return None + if val == -32768: + return -1 + if val == -32767: + return -2 + if val == 32768: + return None + return val / self._divider + + def calcPercentage(self,val: float): + if val == 65535: + return None + return val / self._divider + + @property + def translateVal(self): + # reads an translates a value from the modbua + mbo = ModbusObject(self._config_entry, self._modbus_item) + val = mbo.value + match self._modbus_item.format: + case FORMATS.TEMPERATUR: + return self.calcTemperature(val) + case FORMATS.PERCENTAGE: + return self.calcPercentage(val) + case FORMATS.STATUS: + return self._modbus_item.getTextFromNumber(val) + case _: + if val == None: + return val + return val / self._divider + + @translateVal.setter + def translateVal(self,value): + # translates and writes a value to the modbus + mbo = ModbusObject(self._config_entry, self._modbus_item) + val = None + match self._modbus_item.format: + # logically, this belongs to the ModbusItem, but doing it here + case FORMATS.STATUS: + val = self._modbus_item.getNumberFromText(value) + case _: + val = value * self._divider + mbo.value = val + + def my_device_info(self) -> DeviceInfo: + # helper to build the device info + return { + "identifiers": {(CONST.DOMAIN, self._dev_device)}, + "name": self._dev_device, + "sw_version": "Device_SW_Version", + "model": "Device_model", + "manufacturer": "Weishaupt", + } + +class MySensorEntity(SensorEntity, MyEntity): + # class that represents a sensor entity derived from Sensorentity + # and decorated with general parameters from MyEntity + _attr_native_unit_of_measurement = None + _attr_device_class = None + _attr_state_class = None + + def __init__(self, config_entry, modbus_item) -> None: + MyEntity.__init__(self, config_entry, modbus_item) + + async def async_update(self) -> None: + # the synching is done by the ModbusObject of the entity + self._attr_native_value = self.translateVal + + @property + def device_info(self) -> DeviceInfo: + return MyEntity.my_device_info(self) + +class MyNumberEntity(NumberEntity, MyEntity): + # class that represents a sensor entity derived from Sensorentity + # and decorated with general parameters from MyEntity + _attr_native_unit_of_measurement = None + _attr_device_class = None + _attr_state_class = None + _attr_native_min_value = 10 + _attr_native_max_value = 60 + + + def __init__(self, config_entry, modbus_item) -> None: + MyEntity.__init__(self, config_entry, modbus_item) + + if self._modbus_item.resultlist != None: + self._attr_native_min_value = self._modbus_item.getNumberFromText("min") + self._attr_native_max_value = self._modbus_item.getNumberFromText("max") + self._attr_native_step = self._modbus_item.getNumberFromText("step") + + async def async_set_native_value(self, value: float) -> None: + self.translateVal = value + self._attr_native_value = self.translateVal + self.async_write_ha_state() + + async def async_update(self) -> None: + # the synching is done by the ModbusObject of the entity + self._attr_native_value = self.translateVal + + @property + def device_info(self) -> DeviceInfo: + return MyEntity.my_device_info(self) + + +class MySelectEntity(SelectEntity, MyEntity): + # class that represents a sensor entity derived from Sensorentity + # and decorated with general parameters from MyEntity + options = [] + _attr_current_option = "FEHLER" + + def __init__(self, config_entry, modbus_item) -> None: + MyEntity.__init__(self, config_entry, modbus_item) + self.async_internal_will_remove_from_hass_port = self._config_entry.data[CONF_PORT] + # option list build from the status list of the ModbusItem + self.options = [] + for index, item in enumerate(self._modbus_item._resultlist): + self.options.append(item.text) + + async def async_select_option(self, option: str) -> None: + # the synching is done by the ModbusObject of the entity + self.translateVal = option + self._attr_current_option = self.translateVal + self.async_write_ha_state() + + async def async_update(self) -> None: + # the synching is done by the ModbusObject of the entity + # await self.coordinator.async_request_refresh() + self._attr_current_option = self.translateVal + + @property + def device_info(self) -> DeviceInfo: + return MyEntity.my_device_info(self) diff --git a/custom_components/weishaupt_modbus/hpconst - Kopie.py b/custom_components/weishaupt_modbus/hpconst - Kopie.py new file mode 100644 index 0000000..01419d0 --- /dev/null +++ b/custom_components/weishaupt_modbus/hpconst - Kopie.py @@ -0,0 +1,338 @@ +from dataclasses import dataclass +from .const import TYPES, FORMATS +from .items import ModbusItem, StatusItem + +@dataclass(frozen=True) +class DeviceConstants: + SYS = "System" + WP = "Wärmepumpe" + WW = "Warmwasser" + HZ = "Heizkreis" + W2 = "2. Wärmeerzeuger" + ST = "Statistik" + +DEVICES = DeviceConstants() + +############################################################################################################################## +# Listen mit Fehlermeldungen, Warnmeldungen und Statustexte +# Beschreibungstext ist ebenfalls m�glich +# class StatusItem(): def __init__(self, number, text, description = None): +############################################################################################################################## + +SYS_FEHLER = [ + StatusItem(65535,"kein Fehler"), +] + +SYS_WARNUNG = [ + StatusItem(65535,"keine Warnung"), + StatusItem(32,"Fehler Kältesatz (32)"), +] + +SYS_FEHLERFREI = [ + StatusItem(0,"Fehler aktiv"), + StatusItem(1,"Störungsfreier Betrieb"), +] + +SYS_BETRIEBSANZEIGE = [ + StatusItem(0,"undefiniert"), + StatusItem(1,"Relaistest"), + StatusItem(2,"Notaus"), + StatusItem(3,"Diagnose"), + StatusItem(4,"Handbetrieb"), + StatusItem(5,"Handbetrieb Heizen"), + StatusItem(6,"Handbetrieb Kühlen"), + StatusItem(7,"Manueller Abtaubetrieb"), + StatusItem(8,"Abtauen"), + StatusItem(9,"2. WEZ"), + StatusItem(10,"EVU_SPERRE"), + StatusItem(11,"SG Tarif"), + StatusItem(12,"SG Maximal"), + StatusItem(13,"Tarifladung"), + StatusItem(14,"Erhöhter Betrieb"), + StatusItem(15,"Standzeit"), + StatusItem(16,"Standby"), + StatusItem(17,"Spülen"), + StatusItem(18,"Frostschutz"), + StatusItem(19,"Heizbetrieb"), + StatusItem(20,"Warmwasserbetrieb"), + StatusItem(21,"Legionellenschutz"), + StatusItem(22,"Umschaltung HZ KU"), + StatusItem(23,"Kühlbetrieb"), + StatusItem(24,"Passive Kühlung"), + StatusItem(25,"Sommerbetrieb"), + StatusItem(26,"Schwimmbadbetrieb"), + StatusItem(27,"Urlaub"), + StatusItem(28,"Estrichprogramm"), + StatusItem(29,"Gesperrt"), + StatusItem(30,"Sperre AT"), + StatusItem(31,"Sperre Sommer"), + StatusItem(32,"Sperre Winter"), + StatusItem(33,"Einsatzgrenze"), + StatusItem(34,"HK Sperre"), + StatusItem(35,"Absenkbetrieb"), + StatusItem(43,"Ölrückführung"), +] + +SYS_BETRIEBSART = [ + StatusItem(0,"Automatik"), + StatusItem(1,"Heizen"), + StatusItem(2,"Kühlen"), + StatusItem(3,"Sommer"), + StatusItem(4,"Standby"), + StatusItem(5,"2.WEZ"), +] + +HP_BETRIEB = [ + StatusItem(0,"Undefiniert"), + StatusItem(1,"Relaistest"), + StatusItem(2,"Notaus"), + StatusItem(3,"Diagnose"), + StatusItem(4,"Handbetrieb"), + StatusItem(5,"Handbetrieb Heizen"), + StatusItem(6,"Handbetrieb Kühlen"), + StatusItem(7,"Manueller Abtaubetrieb"), + StatusItem(8,"Abtauen"), + StatusItem(9,"WEZ2"), + StatusItem(10,"EVU_SPERRE"), + StatusItem(11,"SG Tarif"), + StatusItem(12,"SG Maximal"), + StatusItem(13,"Tarifladung"), + StatusItem(14,"Erhöhter Betrieb"), + StatusItem(15,"Standzeit"), + StatusItem(16,"Standbybetrieb"), + StatusItem(17,"Spülbetrieb"), + StatusItem(18,"Frostschutz"), + StatusItem(19,"Heizbetrieb"), + StatusItem(20,"Warmwasserbetrieb"), + StatusItem(21,"Legionellenschutz"), + StatusItem(22,"Umschaltung HZ KU"), + StatusItem(23,"Kühlbetrieb"), + StatusItem(24,"Passive Kühlung"), + StatusItem(25,"Sommerbetrieb"), + StatusItem(26,"Schwimmbad"), + StatusItem(27,"Urlaub"), + StatusItem(28,"Estrich"), + StatusItem(29,"Gesperrt"), + StatusItem(30,"Sperre AT"), + StatusItem(31,"Sperre Sommer"), + StatusItem(32,"Sperre Winter"), + StatusItem(33,"Einsatzgrenze"), + StatusItem(34,"HK Sperre"), + StatusItem(35,"Absenk"), + StatusItem(43,"Ölrückführung"), +] + +HP_STOERMELDUNG = [ + StatusItem(0,"Störung"), + StatusItem(1,"Störungsfrei"), +] + +HP_RUHEMODUS = [ + StatusItem(0,"aus"), + StatusItem(1,"80 %"), + StatusItem(2,"60 %"), + StatusItem(3,"40 %"), +] + +RANGE_PERCENTAGE = [ + StatusItem(0,"min"), + StatusItem(100,"max"), + StatusItem(1,"step"), +] + +TEMPRANGE_ROOM = [ + StatusItem(16,"min"), + StatusItem(30,"max"), + StatusItem(0.5,"step"), +] + +TEMPRANGE_WATER = [ + StatusItem(30,"min"), + StatusItem(60,"max"), + StatusItem(0.5,"step"), +] + +TEMPRANGE_SGREADY = [ + StatusItem(0,"min"), + StatusItem(10,"max"), + StatusItem(0.5,"step"), +] + +TEMPRANGE_BIVALENZ = [ + StatusItem(-20,"min"), + StatusItem(10,"max"), + StatusItem(0.5,"step"), +] + + +RANGE_HZKENNLINIE = [ + StatusItem(0,"min"), + StatusItem(3,"max"), + StatusItem(0.05,"step"), +] + +TIMERANGE_WWPUSH = [ + StatusItem(0,"min"), + StatusItem(240,"max"), + StatusItem(5,"step"), +] + +HZ_KONFIGURATION = [ + StatusItem(0,"aus"), + StatusItem(1,"Pumpenkreis"), + StatusItem(2,"Mischkreis"), + StatusItem(3,"Sollwert (Pumpe M1)"), +] + +HZ_ANFORDERUNG = [ + StatusItem(0,"aus"), + StatusItem(1,"witterungsgeführt"), + StatusItem(2,"konstant"), +] + +HZ_BETRIEBSART = [ + StatusItem(0,"Automatik"), + StatusItem(1,"Komfort"), + StatusItem(2,"Normal"), + StatusItem(3,"Absenkbetrieb"), + StatusItem(4,"Standby"), +] + +HZ_PARTY_PAUSE = [ + StatusItem(1,"Pause 12.0h"), + StatusItem(2,"Pause 11.5h"), + StatusItem(3,"Pause 11.0h"), + StatusItem(4,"Pause 10.5h"), + StatusItem(5,"Pause 10.0h"), + StatusItem(6,"Pause 9.5h"), + StatusItem(7,"Pause 9.0h"), + StatusItem(8,"Pause 8.5h"), + StatusItem(9,"Pause 8.0h"), + StatusItem(10,"Pause 7.5h"), + StatusItem(11,"Pause 7.0h"), + StatusItem(12,"Pause 6.5h"), + StatusItem(13,"Pause 6.0h"), + StatusItem(14,"Pause 5.5h"), + StatusItem(15,"Pause 5.0h"), + StatusItem(16,"Pause 4.5h"), + StatusItem(17,"Pause 4.0h"), + StatusItem(18,"Pause 3.5h"), + StatusItem(19,"Pause 3.0h"), + StatusItem(20,"Pause 2.5h"), + StatusItem(21,"Pause 2.0h"), + StatusItem(22,"Pause 1.5h"), + StatusItem(23,"Pause 1.0h"), + StatusItem(24,"Pause 0.5h"), + StatusItem(25,"Automatik"), + StatusItem(26,"Party 0.5h"), + StatusItem(27,"Party 1.0h"), + StatusItem(28,"Party 1.5h"), + StatusItem(29,"Party 2.0h"), + StatusItem(30,"Party 2.5h"), + StatusItem(31,"Party 3.0h"), + StatusItem(32,"Party 3.5h"), + StatusItem(33,"Party 4.0h"), + StatusItem(34,"Party 4.5h"), + StatusItem(35,"Party 5.0h"), + StatusItem(36,"Party 5.5h"), + StatusItem(37,"Party 6.0h"), + StatusItem(38,"Party 6.5h"), + StatusItem(39,"Party 7.0h"), + StatusItem(40,"Party 7.5h"), + StatusItem(41,"Party 8.0h"), + StatusItem(42,"Party 8.5h"), + StatusItem(43,"Party 9.0h"), + StatusItem(44,"Party 9.5h"), + StatusItem(45,"Party 10.0h"), + StatusItem(46,"Party 10.5h"), + StatusItem(47,"Party 11.0h"), + StatusItem(48,"Party 11.5h"), + StatusItem(49,"Party 12.0h"), +] + +WW_KONFIGURATION = [ + StatusItem(0,"aus"), + StatusItem(1,"Umlenkventil"), + StatusItem(2,"Pumpe"), +] + +W2_STATUS = [ + StatusItem(0,"aus"), + StatusItem(1,"ein"), +] + +W2_KONFIG = [ + StatusItem(0,"0"), + StatusItem(1,"1"), +] + + +############################################################################################################################## +# Modbus Register List: # +# https://docs.google.com/spreadsheets/d/1EZ3QgyB41xaXo4B5CfZe0Pi8KPwzIGzK/edit?gid=1730751621#gid=1730751621 # +############################################################################################################################## + +MODBUS_SYS_ITEMS = [ + ModbusItem(30001,"Aussentemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.SYS), + ModbusItem(30002,"Luftansaugtemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.SYS), + ModbusItem(30003,"Fehler",FORMATS.STATUS,TYPES.SENSOR,DEVICES.SYS, SYS_FEHLER), + ModbusItem(30004,"Warnung",FORMATS.STATUS,TYPES.SENSOR,DEVICES.SYS, SYS_WARNUNG), + ModbusItem(30005,"Fehlerfrei",FORMATS.STATUS,TYPES.SENSOR,DEVICES.SYS, SYS_FEHLERFREI), + ModbusItem(30006,"Betriebsanzeige",FORMATS.STATUS,TYPES.SENSOR,DEVICES.SYS, SYS_BETRIEBSANZEIGE), + ModbusItem(40001,"Systembetriebsart",FORMATS.STATUS,TYPES.SELECT,DEVICES.SYS, SYS_BETRIEBSART), + + ModbusItem(33101,"Betrieb",FORMATS.STATUS,TYPES.SENSOR,DEVICES.WP, HP_BETRIEB), + ModbusItem(33102,"Störmeldung",FORMATS.STATUS,TYPES.SENSOR,DEVICES.WP, HP_STOERMELDUNG), + ModbusItem(33103,"Leistungsanforderung",FORMATS.PERCENTAGE,TYPES.SENSOR,DEVICES.WP), + ModbusItem(33104,"Vorlauftemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.WP), + ModbusItem(33105,"Rücklauftemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.WP), + ModbusItem(43101,"Konfiguration ",FORMATS.NUMBER,TYPES.NUMBER,DEVICES.WP), + ModbusItem(43102,"Ruhemodus",FORMATS.STATUS,TYPES.SELECT,DEVICES.WP,HP_RUHEMODUS), + ModbusItem(43103,"Pumpe Einschaltart",FORMATS.NUMBER,TYPES.NUMBER,DEVICES.WP), + ModbusItem(43104,"Pumpe Leistung Heizen",FORMATS.PERCENTAGE,TYPES.NUMBER,DEVICES.WP,RANGE_PERCENTAGE), + ModbusItem(43105,"Pumpe Leistung Kühlen",FORMATS.PERCENTAGE,TYPES.NUMBER,DEVICES.WP,RANGE_PERCENTAGE), + ModbusItem(43106,"Pumpe Leistung Warmwasser",FORMATS.PERCENTAGE,TYPES.NUMBER,DEVICES.WP,RANGE_PERCENTAGE), + ModbusItem(43107,"Pumpe Leistung Abtaubetrieb",FORMATS.PERCENTAGE,TYPES.NUMBER,DEVICES.WP,RANGE_PERCENTAGE), + ModbusItem(43108,"Volumenstrom Heizen",FORMATS.VOLUMENSTROM,TYPES.NUMBER,DEVICES.WP), + ModbusItem(43109,"Volumenstrom Kühlen",FORMATS.VOLUMENSTROM,TYPES.NUMBER,DEVICES.WP), + ModbusItem(43110,"Volumenstrom Warmwasser",FORMATS.VOLUMENSTROM,TYPES.NUMBER,DEVICES.WP), + + ModbusItem(31101,"Raumsolltemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.HZ), + ModbusItem(31102,"Raumtemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.HZ), + ModbusItem(31103,"Raumfeuchte",FORMATS.PERCENTAGE,TYPES.SENSOR,DEVICES.HZ), + ModbusItem(31104,"Vorlaufsolltemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.HZ), + ModbusItem(31105,"HZ_Vorlauftemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.HZ), + ModbusItem(41101,"HZ_Konfiguration",FORMATS.STATUS,TYPES.SELECT,DEVICES.HZ, HZ_KONFIGURATION), + ModbusItem(41102,"Anforderung Typ",FORMATS.STATUS,TYPES.SELECT,DEVICES.HZ, HZ_ANFORDERUNG), + ModbusItem(41103,"Betriebsart",FORMATS.STATUS,TYPES.SELECT,DEVICES.HZ, HZ_BETRIEBSART), + ModbusItem(41104,"Pause / Party",FORMATS.STATUS,TYPES.SELECT,DEVICES.HZ, HZ_PARTY_PAUSE), + ModbusItem(41105,"Raumsolltemperatur Komfort",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41106,"Raumsolltemperatur Normal",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41107,"Raumsolltemperatur Absenk",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41108,"Heizkennlinie",FORMATS.KENNLINIE,TYPES.NUMBER,DEVICES.HZ,RANGE_HZKENNLINIE), + ModbusItem(41109,"Sommer Winter Umschaltung",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41110,"Heizen Konstanttemperatur",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41111,"Heizen Konstanttemp Absenk",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41112,"Kühlen Konstanttemperatur",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + + ModbusItem(32101,"Warmwassersolltemperatur",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.WW,TEMPRANGE_WATER), + ModbusItem(32102,"Warmwassertemepratur",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.WW,TEMPRANGE_WATER), + ModbusItem(42101,"WW_Konfiguration",FORMATS.STATUS,TYPES.SELECT,DEVICES.WW,WW_KONFIGURATION), + ModbusItem(42102,"Warmwasser Push",FORMATS.TIME_MIN,TYPES.NUMBER,DEVICES.WW,TIMERANGE_WWPUSH), + ModbusItem(42103,"Warmwasser Normal",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.WW,TEMPRANGE_WATER), + ModbusItem(42104,"Warmwasser Absenk",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.WW,TEMPRANGE_WATER), + ModbusItem(42105,"SG Ready Anhebung",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.WW,TEMPRANGE_SGREADY), + + ModbusItem(34101,"Status 2. WEZ",FORMATS.STATUS,TYPES.SENSOR,DEVICES.W2,W2_STATUS), + ModbusItem(34102,"Betriebsstunden 2. WEZ",FORMATS.TIME_H,TYPES.SENSOR,DEVICES.W2), + ModbusItem(34103,"Schaltspiele 2. WEZ",FORMATS.NUMBER,TYPES.SENSOR,DEVICES.W2), + ModbusItem(34104,"Status E-Heizung 1",FORMATS.STATUS,TYPES.SENSOR,DEVICES.W2,W2_STATUS), + ModbusItem(34105,"Status E-Heizung 2",FORMATS.STATUS,TYPES.SENSOR,DEVICES.W2,W2_STATUS), + ModbusItem(34106,"Betriebsstunden E1",FORMATS.TIME_H,TYPES.SENSOR,DEVICES.W2), + ModbusItem(34107,"Betriebsstunden E2",FORMATS.TIME_H,TYPES.SENSOR,DEVICES.W2), + ModbusItem(44101,"W2_Konfiguration",FORMATS.STATUS,TYPES.SENSOR,DEVICES.W2,W2_KONFIG), + ModbusItem(44102,"Grenztemperatur",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.W2,TEMPRANGE_BIVALENZ), + ModbusItem(44103,"Bivalenztemperatur",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.W2,TEMPRANGE_BIVALENZ), + ModbusItem(44104,"Bivalenztemperatur WW",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.W2,TEMPRANGE_BIVALENZ), + +] diff --git a/custom_components/weishaupt_modbus/hpconst.py b/custom_components/weishaupt_modbus/hpconst.py new file mode 100644 index 0000000..3c1c759 --- /dev/null +++ b/custom_components/weishaupt_modbus/hpconst.py @@ -0,0 +1,402 @@ +from dataclasses import dataclass +from .const import TYPES, FORMATS +from .items import ModbusItem, StatusItem +from homeassistant.components.sensor import SensorDeviceClass + +@dataclass(frozen=True) +class DeviceConstants: + SYS = "System" + WP = "Wärmepumpe" + WW = "Warmwasser" + HZ = "Heizkreis" + W2 = "2. Wärmeerzeuger" + ST = "Statistik" + +DEVICES = DeviceConstants() + +############################################################################################################################## +# Listen mit Fehlermeldungen, Warnmeldungen und Statustexte +# Beschreibungstext ist ebenfalls m�glich +# class StatusItem(): def __init__(self, number, text, description = None): +############################################################################################################################## + +SYS_FEHLER = [ + StatusItem(65535,"kein Fehler"), +] + +SYS_WARNUNG = [ + StatusItem(65535,"keine Warnung"), + StatusItem(32,"Fehler Kältesatz (32)"), +] + +SYS_FEHLERFREI = [ + StatusItem(0,"Fehler aktiv"), + StatusItem(1,"Störungsfreier Betrieb"), +] + +SYS_BETRIEBSANZEIGE = [ + StatusItem(0,"undefiniert"), + StatusItem(1,"Relaistest"), + StatusItem(2,"Notaus"), + StatusItem(3,"Diagnose"), + StatusItem(4,"Handbetrieb"), + StatusItem(5,"Handbetrieb Heizen"), + StatusItem(6,"Handbetrieb Kühlen"), + StatusItem(7,"Manueller Abtaubetrieb"), + StatusItem(8,"Abtauen"), + StatusItem(9,"2. WEZ"), + StatusItem(10,"EVU_SPERRE"), + StatusItem(11,"SG Tarif"), + StatusItem(12,"SG Maximal"), + StatusItem(13,"Tarifladung"), + StatusItem(14,"Erhöhter Betrieb"), + StatusItem(15,"Standzeit"), + StatusItem(16,"Standby"), + StatusItem(17,"Spülen"), + StatusItem(18,"Frostschutz"), + StatusItem(19,"Heizbetrieb"), + StatusItem(20,"Warmwasserbetrieb"), + StatusItem(21,"Legionellenschutz"), + StatusItem(22,"Umschaltung HZ KU"), + StatusItem(23,"Kühlbetrieb"), + StatusItem(24,"Passive Kühlung"), + StatusItem(25,"Sommerbetrieb"), + StatusItem(26,"Schwimmbadbetrieb"), + StatusItem(27,"Urlaub"), + StatusItem(28,"Estrichprogramm"), + StatusItem(29,"Gesperrt"), + StatusItem(30,"Sperre AT"), + StatusItem(31,"Sperre Sommer"), + StatusItem(32,"Sperre Winter"), + StatusItem(33,"Einsatzgrenze"), + StatusItem(34,"HK Sperre"), + StatusItem(35,"Absenkbetrieb"), + StatusItem(43,"Ölrückführung"), +] + +SYS_BETRIEBSART = [ + StatusItem(0,"Automatik"), + StatusItem(1,"Heizen"), + StatusItem(2,"Kühlen"), + StatusItem(3,"Sommer"), + StatusItem(4,"Standby"), + StatusItem(5,"2.WEZ"), +] + +HP_BETRIEB = [ + StatusItem(0,"Undefiniert"), + StatusItem(1,"Relaistest"), + StatusItem(2,"Notaus"), + StatusItem(3,"Diagnose"), + StatusItem(4,"Handbetrieb"), + StatusItem(5,"Handbetrieb Heizen"), + StatusItem(6,"Handbetrieb Kühlen"), + StatusItem(7,"Manueller Abtaubetrieb"), + StatusItem(8,"Abtauen"), + StatusItem(9,"WEZ2"), + StatusItem(10,"EVU_SPERRE"), + StatusItem(11,"SG Tarif"), + StatusItem(12,"SG Maximal"), + StatusItem(13,"Tarifladung"), + StatusItem(14,"Erhöhter Betrieb"), + StatusItem(15,"Standzeit"), + StatusItem(16,"Standbybetrieb"), + StatusItem(17,"Spülbetrieb"), + StatusItem(18,"Frostschutz"), + StatusItem(19,"Heizbetrieb"), + StatusItem(20,"Warmwasserbetrieb"), + StatusItem(21,"Legionellenschutz"), + StatusItem(22,"Umschaltung HZ KU"), + StatusItem(23,"Kühlbetrieb"), + StatusItem(24,"Passive Kühlung"), + StatusItem(25,"Sommerbetrieb"), + StatusItem(26,"Schwimmbad"), + StatusItem(27,"Urlaub"), + StatusItem(28,"Estrich"), + StatusItem(29,"Gesperrt"), + StatusItem(30,"Sperre AT"), + StatusItem(31,"Sperre Sommer"), + StatusItem(32,"Sperre Winter"), + StatusItem(33,"Einsatzgrenze"), + StatusItem(34,"HK Sperre"), + StatusItem(35,"Absenk"), + StatusItem(43,"Ölrückführung"), +] + +HP_STOERMELDUNG = [ + StatusItem(0,"Störung"), + StatusItem(1,"Störungsfrei"), +] + +HP_RUHEMODUS = [ + StatusItem(0,"aus"), + StatusItem(1,"80 %"), + StatusItem(2,"60 %"), + StatusItem(3,"40 %"), +] + +HZ_KONFIGURATION = [ + StatusItem(0,"aus"), + StatusItem(1,"Pumpenkreis"), + StatusItem(2,"Mischkreis"), + StatusItem(3,"Sollwert (Pumpe M1)"), +] + +HZ_ANFORDERUNG = [ + StatusItem(0,"aus"), + StatusItem(1,"witterungsgeführt"), + StatusItem(2,"konstant"), +] + +HZ_BETRIEBSART = [ + StatusItem(0,"Automatik"), + StatusItem(1,"Komfort"), + StatusItem(2,"Normal"), + StatusItem(3,"Absenkbetrieb"), + StatusItem(4,"Standby"), +] + +HZ_PARTY_PAUSE = [ + StatusItem(1,"Pause 12.0h"), + StatusItem(2,"Pause 11.5h"), + StatusItem(3,"Pause 11.0h"), + StatusItem(4,"Pause 10.5h"), + StatusItem(5,"Pause 10.0h"), + StatusItem(6,"Pause 9.5h"), + StatusItem(7,"Pause 9.0h"), + StatusItem(8,"Pause 8.5h"), + StatusItem(9,"Pause 8.0h"), + StatusItem(10,"Pause 7.5h"), + StatusItem(11,"Pause 7.0h"), + StatusItem(12,"Pause 6.5h"), + StatusItem(13,"Pause 6.0h"), + StatusItem(14,"Pause 5.5h"), + StatusItem(15,"Pause 5.0h"), + StatusItem(16,"Pause 4.5h"), + StatusItem(17,"Pause 4.0h"), + StatusItem(18,"Pause 3.5h"), + StatusItem(19,"Pause 3.0h"), + StatusItem(20,"Pause 2.5h"), + StatusItem(21,"Pause 2.0h"), + StatusItem(22,"Pause 1.5h"), + StatusItem(23,"Pause 1.0h"), + StatusItem(24,"Pause 0.5h"), + StatusItem(25,"Automatik"), + StatusItem(26,"Party 0.5h"), + StatusItem(27,"Party 1.0h"), + StatusItem(28,"Party 1.5h"), + StatusItem(29,"Party 2.0h"), + StatusItem(30,"Party 2.5h"), + StatusItem(31,"Party 3.0h"), + StatusItem(32,"Party 3.5h"), + StatusItem(33,"Party 4.0h"), + StatusItem(34,"Party 4.5h"), + StatusItem(35,"Party 5.0h"), + StatusItem(36,"Party 5.5h"), + StatusItem(37,"Party 6.0h"), + StatusItem(38,"Party 6.5h"), + StatusItem(39,"Party 7.0h"), + StatusItem(40,"Party 7.5h"), + StatusItem(41,"Party 8.0h"), + StatusItem(42,"Party 8.5h"), + StatusItem(43,"Party 9.0h"), + StatusItem(44,"Party 9.5h"), + StatusItem(45,"Party 10.0h"), + StatusItem(46,"Party 10.5h"), + StatusItem(47,"Party 11.0h"), + StatusItem(48,"Party 11.5h"), + StatusItem(49,"Party 12.0h"), +] + +WW_KONFIGURATION = [ + StatusItem(0,"aus"), + StatusItem(1,"Umlenkventil"), + StatusItem(2,"Pumpe"), +] + +W2_STATUS = [ + StatusItem(0,"aus"), + StatusItem(1,"ein"), +] + +W2_KONFIG = [ + StatusItem(0,"0"), + StatusItem(1,"1"), +] + +##################################################### +# Description of physical units via the status list # +##################################################### + +RANGE_PERCENTAGE = [ + StatusItem(0,"min"), + StatusItem(100,"max"), + StatusItem(1,"step"), + StatusItem(1,"divider"), +] + +TEMPRANGE_ROOM = [ + StatusItem(16,"min"), + StatusItem(30,"max"), + StatusItem(0.5,"step"), + StatusItem(10,"divider"), + StatusItem(-1,SensorDeviceClass.TEMPERATURE), +] + +TEMPRANGE_WATER = [ + StatusItem(30,"min"), + StatusItem(60,"max"), + StatusItem(0.5,"step"), + StatusItem(10,"divider"), + StatusItem(-1,SensorDeviceClass.TEMPERATURE), +] + +TEMPRANGE_SGREADY = [ + StatusItem(0,"min"), + StatusItem(10,"max"), + StatusItem(0.5,"step"), + StatusItem(10,"divider"), + StatusItem(-1,SensorDeviceClass.TEMPERATURE), +] + +TEMPRANGE_BIVALENZ = [ + StatusItem(-20,"min"), + StatusItem(10,"max"), + StatusItem(0.5,"step"), + StatusItem(10,"divider"), + StatusItem(-1,SensorDeviceClass.TEMPERATURE), +] + +TEMPRANGE_STD = [ + StatusItem(-60,"min"), + StatusItem(100,"max"), + StatusItem(0.5,"step"), + StatusItem(10,"divider"), + StatusItem(-1,SensorDeviceClass.TEMPERATURE), +] + + +RANGE_HZKENNLINIE = [ + StatusItem(0,"min"), + StatusItem(3,"max"), + StatusItem(0.05,"step"), + StatusItem(100,"divider"), +] + +TIMERANGE_WWPUSH = [ + StatusItem(0,"min"), + StatusItem(240,"max"), + StatusItem(5,"step"), + StatusItem(1,"divider"), +] + +RANGE_FLOWRATE = [ + StatusItem(0,"min"), + StatusItem(3,"max"), + StatusItem(0.1,"step"), + StatusItem(100,"divider"), +] + +RANGE_ENERGY = [ + StatusItem(-1,SensorDeviceClass.ENERGY), + StatusItem(1,"divider"), +] + + +############################################################################################################################## +# Modbus Register List: # +# https://docs.google.com/spreadsheets/d/1EZ3QgyB41xaXo4B5CfZe0Pi8KPwzIGzK/edit?gid=1730751621#gid=1730751621 # +############################################################################################################################## + +MODBUS_SYS_ITEMS = [ + ModbusItem(30001,"Aussentemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.SYS, TEMPRANGE_STD), + ModbusItem(30002,"Luftansaugtemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.SYS, TEMPRANGE_STD), + ModbusItem(30003,"Fehler",FORMATS.STATUS,TYPES.SENSOR,DEVICES.SYS, SYS_FEHLER), + ModbusItem(30004,"Warnung",FORMATS.STATUS,TYPES.SENSOR,DEVICES.SYS, SYS_WARNUNG), + ModbusItem(30005,"Fehlerfrei",FORMATS.STATUS,TYPES.SENSOR,DEVICES.SYS, SYS_FEHLERFREI), + ModbusItem(30006,"Betriebsanzeige",FORMATS.STATUS,TYPES.SENSOR,DEVICES.SYS, SYS_BETRIEBSANZEIGE), + ModbusItem(40001,"Systembetriebsart",FORMATS.STATUS,TYPES.SELECT,DEVICES.SYS, SYS_BETRIEBSART), + + ModbusItem(33101,"Betrieb",FORMATS.STATUS,TYPES.SENSOR,DEVICES.WP, HP_BETRIEB), + ModbusItem(33102,"Störmeldung",FORMATS.STATUS,TYPES.SENSOR,DEVICES.WP, HP_STOERMELDUNG), + ModbusItem(33103,"Leistungsanforderung",FORMATS.PERCENTAGE,TYPES.SENSOR,DEVICES.WP), + ModbusItem(33104,"Vorlauftemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.WP, TEMPRANGE_STD), + ModbusItem(33105,"Rücklauftemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.WP, TEMPRANGE_STD), + ModbusItem(43101,"Konfiguration ",FORMATS.NUMBER,TYPES.NUMBER_RO,DEVICES.WP), + ModbusItem(43102,"Ruhemodus",FORMATS.STATUS,TYPES.NUMBER_RO,DEVICES.WP,HP_RUHEMODUS), + ModbusItem(43103,"Pumpe Einschaltart",FORMATS.NUMBER,TYPES.NUMBER_RO,DEVICES.WP), + ModbusItem(43104,"Pumpe Leistung Heizen",FORMATS.PERCENTAGE,TYPES.NUMBER_RO,DEVICES.WP,RANGE_PERCENTAGE), + ModbusItem(43105,"Pumpe Leistung Kühlen",FORMATS.PERCENTAGE,TYPES.NUMBER_RO,DEVICES.WP,RANGE_PERCENTAGE), + ModbusItem(43106,"Pumpe Leistung Warmwasser",FORMATS.PERCENTAGE,TYPES.NUMBER_RO,DEVICES.WP,RANGE_PERCENTAGE), + ModbusItem(43107,"Pumpe Leistung Abtaubetrieb",FORMATS.PERCENTAGE,TYPES.NUMBER_RO,DEVICES.WP,RANGE_PERCENTAGE), + ModbusItem(43108,"Volumenstrom Heizen",FORMATS.VOLUMENSTROM,TYPES.NUMBER_RO,DEVICES.WP, RANGE_FLOWRATE), + ModbusItem(43109,"Volumenstrom Kühlen",FORMATS.VOLUMENSTROM,TYPES.NUMBER_RO,DEVICES.WP,RANGE_FLOWRATE), + ModbusItem(43110,"Volumenstrom Warmwasser",FORMATS.VOLUMENSTROM,TYPES.NUMBER_RO,DEVICES.WP,RANGE_FLOWRATE), + + ModbusItem(31101,"Raumsolltemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.HZ, TEMPRANGE_ROOM), + ModbusItem(31102,"Raumtemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.HZ, TEMPRANGE_ROOM), + ModbusItem(31103,"Raumfeuchte",FORMATS.PERCENTAGE,TYPES.SENSOR,DEVICES.HZ), + ModbusItem(31104,"Vorlaufsolltemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.HZ, TEMPRANGE_STD), + ModbusItem(31105,"HZ_Vorlauftemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.HZ, TEMPRANGE_STD), + ModbusItem(41101,"HZ_Konfiguration",FORMATS.STATUS,TYPES.NUMBER_RO,DEVICES.HZ, HZ_KONFIGURATION), + ModbusItem(41102,"Anforderung Typ",FORMATS.STATUS,TYPES.NUMBER_RO,DEVICES.HZ, HZ_ANFORDERUNG), + ModbusItem(41103,"Betriebsart",FORMATS.STATUS,TYPES.SELECT,DEVICES.HZ, HZ_BETRIEBSART), + ModbusItem(41104,"Pause / Party",FORMATS.STATUS,TYPES.SELECT,DEVICES.HZ, HZ_PARTY_PAUSE), + ModbusItem(41105,"Raumsolltemperatur Komfort",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41106,"Raumsolltemperatur Normal",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41107,"Raumsolltemperatur Absenk",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41108,"Heizkennlinie",FORMATS.KENNLINIE,TYPES.NUMBER,DEVICES.HZ,RANGE_HZKENNLINIE), + ModbusItem(41109,"Sommer Winter Umschaltung",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41110,"Heizen Konstanttemperatur",FORMATS.TEMPERATUR,TYPES.NUMBER_RO,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41111,"Heizen Konstanttemp Absenk",FORMATS.TEMPERATUR,TYPES.NUMBER_RO,DEVICES.HZ,TEMPRANGE_ROOM), + ModbusItem(41112,"Kühlen Konstanttemperatur",FORMATS.TEMPERATUR,TYPES.NUMBER_RO,DEVICES.HZ,TEMPRANGE_ROOM), + + ModbusItem(32101,"Warmwassersolltemperatur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.WW,TEMPRANGE_WATER), + ModbusItem(32102,"Warmwassertemepratur",FORMATS.TEMPERATUR,TYPES.SENSOR,DEVICES.WW,TEMPRANGE_WATER), + ModbusItem(42101,"WW_Konfiguration",FORMATS.STATUS,TYPES.NUMBER_RO,DEVICES.WW,WW_KONFIGURATION), + ModbusItem(42102,"Warmwasser Push",FORMATS.TIME_MIN,TYPES.NUMBER,DEVICES.WW,TIMERANGE_WWPUSH), + ModbusItem(42103,"Warmwasser Normal",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.WW,TEMPRANGE_WATER), + ModbusItem(42104,"Warmwasser Absenk",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.WW,TEMPRANGE_WATER), + ModbusItem(42105,"SG Ready Anhebung",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.WW,TEMPRANGE_SGREADY), + + ModbusItem(34101,"Status 2. WEZ",FORMATS.STATUS,TYPES.SENSOR,DEVICES.W2,W2_STATUS), + ModbusItem(34102,"Betriebsstunden 2. WEZ",FORMATS.TIME_H,TYPES.SENSOR,DEVICES.W2), + ModbusItem(34103,"Schaltspiele 2. WEZ",FORMATS.NUMBER,TYPES.SENSOR,DEVICES.W2), + ModbusItem(34104,"Status E-Heizung 1",FORMATS.STATUS,TYPES.SENSOR,DEVICES.W2,W2_STATUS), + ModbusItem(34105,"Status E-Heizung 2",FORMATS.STATUS,TYPES.SENSOR,DEVICES.W2,W2_STATUS), + ModbusItem(34106,"Betriebsstunden E1",FORMATS.TIME_H,TYPES.SENSOR,DEVICES.W2), + ModbusItem(34107,"Betriebsstunden E2",FORMATS.TIME_H,TYPES.SENSOR,DEVICES.W2), + ModbusItem(44101,"W2_Konfiguration",FORMATS.STATUS,TYPES.SENSOR,DEVICES.W2,W2_KONFIG), + ModbusItem(44102,"Grenztemperatur",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.W2,TEMPRANGE_BIVALENZ), + ModbusItem(44103,"Bivalenztemperatur",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.W2,TEMPRANGE_BIVALENZ), + ModbusItem(44104,"Bivalenztemperatur WW",FORMATS.TEMPERATUR,TYPES.NUMBER,DEVICES.W2,TEMPRANGE_BIVALENZ), + + ModbusItem(36101,"Gesamt Energie heute",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36102,"Gesamt Energie gestern",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36103,"Gesamt Energie Monat",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36104,"Gesamt Energie Jahr",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36201,"Heizen Energie heute",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36202,"Heizen Energie gestern",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36203,"Heizen Energie Monat",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36204,"Heizen Energie Jahr",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36301,"Warmwasser Energie heute",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36302,"Warmwasser Energie gestern",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36303,"Warmwasser Energie Monat",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36304,"Warmwasser Energie Jahr",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36401,"Kühlen Energie heute",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36402,"Kühlen Energie gestern",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36403,"Kühlen Energie Monat",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36404,"Kühlen Energie Jahr",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36501,"Abtauen Energie heute",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36502,"Abtauen Energie gestern",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36503,"Abtauen Energie Monat",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36504,"Abtauen Energie Jahr",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36601,"Gesamt Energie II heute",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36602,"Gesamt Energie II gestern",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36603,"Gesamt Energie II Monat",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36604,"Gesamt Energie II Jahr",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36701,"Elektr. Energie heute",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36702,"Elektr. Energie gestern",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36703,"Elektr. Energie Monat",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), + ModbusItem(36704,"Elektr. Energie Jahr",FORMATS.ENERGY,TYPES.SENSOR,DEVICES.ST,RANGE_ENERGY), +] diff --git a/custom_components/weishaupt_modbus/items - Kopie.py b/custom_components/weishaupt_modbus/items - Kopie.py new file mode 100644 index 0000000..417934a --- /dev/null +++ b/custom_components/weishaupt_modbus/items - Kopie.py @@ -0,0 +1,99 @@ +from .const import TYPES + +# An item of a status, e.g. error code and error text along with a precise description +# A class is intentionally defined here because the assignment via dictionaries would not work so elegantly in the end, +# especially when searching backwards. (At least I don't know how...) +class StatusItem(): + _number = -1 + _text = "" + _description = "" + + def __init__(self, number, text, description = None): + self._number = number + self._text = text + self._description = description + + @property + def number(self): + return self._number + + @number.setter + def number(self, value) -> None: + self._number = value + + @property + def text(self): + return self._text + + @text.setter + def text(self, value) -> None: + self._text = value + + @property + def description(self): + return self._description + + @description.setter + def description(self, value) -> None: + self._description = value + +# A Modbus item, consisting of address, name, +# format (temperature, status, ..), +# type (sensor, number, ..), +# device (System, Heatpump, ..) and +# optional result list from status items +# (number entities: status = limits? +class ModbusItem(): + _address = None + _name = "empty" + _format = None + _type = TYPES.SENSOR + _resultlist = None + _device = None + + def __init__(self, address, name, format, type, device, resultlist = None): + self._address = address + self._name = name + self._format = format + self._type = type + self._device = device + self._resultlist = resultlist + + @property + def address(self): + return self._address + @property + def name(self): + return self._name + + @property + def format(self): + return self._format + + @property + def type(self): + return self._type + + @property + def device(self): + return self._device + + @property + def resultlist(self): + return self._resultlist + + def getTextFromNumber(self, val): + if self._resultlist == None: + return None + for index, item in enumerate(self._resultlist): + if val == item.number: + return item.text + return "unbekannt <" + str(val) + ">" + + def getNumberFromText(self, val): + if self._resultlist == None: + return None + for index, item in enumerate(self._resultlist): + if val == item.text: + return item.number + return -1 diff --git a/custom_components/weishaupt_modbus/items.py b/custom_components/weishaupt_modbus/items.py new file mode 100644 index 0000000..df3e83c --- /dev/null +++ b/custom_components/weishaupt_modbus/items.py @@ -0,0 +1,99 @@ +from .const import TYPES + +# An item of a status, e.g. error code and error text along with a precise description +# A class is intentionally defined here because the assignment via dictionaries would not work so elegantly in the end, +# especially when searching backwards. (At least I don't know how...) +class StatusItem(): + _number = None + _text = None + _description = None + + def __init__(self, number, text, description = None): + self._number = number + self._text = text + self._description = description + + @property + def number(self): + return self._number + + @number.setter + def number(self, value) -> None: + self._number = value + + @property + def text(self): + return self._text + + @text.setter + def text(self, value) -> None: + self._text = value + + @property + def description(self): + return self._description + + @description.setter + def description(self, value) -> None: + self._description = value + +# A Modbus item, consisting of address, name, +# format (temperature, status, ..), +# type (sensor, number, ..), +# device (System, Heatpump, ..) and +# optional result list from status items +# (number entities: status = limits? +class ModbusItem(): + _address = None + _name = "empty" + _format = None + _type = TYPES.SENSOR + _resultlist = None + _device = None + + def __init__(self, address, name, format, type, device, resultlist = None): + self._address = address + self._name = name + self._format = format + self._type = type + self._device = device + self._resultlist = resultlist + + @property + def address(self): + return self._address + @property + def name(self): + return self._name + + @property + def format(self): + return self._format + + @property + def type(self): + return self._type + + @property + def device(self): + return self._device + + @property + def resultlist(self): + return self._resultlist + + def getTextFromNumber(self, val): + if self._resultlist == None: + return None + for index, item in enumerate(self._resultlist): + if val == item.number: + return item.text + return "unbekannt <" + str(val) + ">" + + def getNumberFromText(self, val): + if self._resultlist == None: + return None + for index, item in enumerate(self._resultlist): + if val == item.text: + return item.number + return -1 diff --git a/custom_components/weishaupt_modbus/manifest.json b/custom_components/weishaupt_modbus/manifest.json index 3fc997e..cf16178 100644 --- a/custom_components/weishaupt_modbus/manifest.json +++ b/custom_components/weishaupt_modbus/manifest.json @@ -1,11 +1,11 @@ { - "domain": "weishaupt_modbus", - "name": "Weishaupt Modbus Integration", - "codeowners": ["@MadOne"], + "domain": "weishaupt_wbb", + "name": "Weishaupt WBB", + "codeowners": ["@OStrama"], "config_flow": true, - "documentation": "https://github.com/MadOne/weishaupt_modbus/", + "documentation": "https://github.com/OStrama/weishaupt_modbus/", "iot_class": "local_polling", - "issue_tracker": "https://github.com/MadOne/weishaupt_modbus/issues", + "issue_tracker": "https://github.com/OStrama/weishaupt_modbus/issues", "requirements": [], "version": "0.0.1" } diff --git a/custom_components/weishaupt_modbus/modbusobject.py b/custom_components/weishaupt_modbus/modbusobject.py new file mode 100644 index 0000000..f6a364d --- /dev/null +++ b/custom_components/weishaupt_modbus/modbusobject.py @@ -0,0 +1,55 @@ +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_PORT +from pymodbus.client import ModbusTcpClient as ModbusClient +from .const import FORMATS, TYPES + +# import logging +# logging.basicConfig() +# log = logging.getLogger() +# log.setLevel(logging.DEBUG) + +# A Modbus object that contains a Modbus item and communicates with the Modbus +# it contains a ModbusClient for setting and getting Modbus register values +class ModbusObject(): + _ModbusItem = None + _DataFormat = None + + _ip = None + _port = None + _ModbusClient = None + + def __init__(self, config_entry, modbus_item): + self._ModbusItem = modbus_item + #self._HeatPump = heatpump + + self._ip = config_entry.data[CONF_HOST] + self._port = config_entry.data[CONF_PORT] + self._ModbusClient = None + + def connect(self): + try: + self._ModbusClient = ModbusClient(host=self._ip, port=self._port) + return self._ModbusClient.connected # noqa: TRY300 + except: # noqa: E722 + return None + + @property + def value(self): + try: + self.connect() + match self._ModbusItem.type: + case TYPES.SENSOR: + # Sensor entities are read-only + return self._ModbusClient.read_input_registers(self._ModbusItem.address, slave=1).registers[0] + case TYPES.SELECT | TYPES.NUMBER | TYPES.NUMBER_RO: + return self._ModbusClient.read_holding_registers(self._ModbusItem.address, slave=1).registers[0] + except: # noqa: E722 + return None + + @value.setter + def value(self,value) -> None: + if self._ModbusItem.type == TYPES.SENSOR | self._ModbusItem.type == TYPES.NUMBER_RO: + # Sensor entities are read-only + return + self.connect() + self._ModbusClient.write_register(self._ModbusItem.address, int(value), slave=1) diff --git a/custom_components/weishaupt_modbus/number.py b/custom_components/weishaupt_modbus/number.py index 5487f33..14106d6 100644 --- a/custom_components/weishaupt_modbus/number.py +++ b/custom_components/weishaupt_modbus/number.py @@ -1,617 +1,27 @@ -"""Number platform for wemportal component.""" +from __future__ import annotations -from homeassistant.components.number import NumberEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT, UnitOfTemperature, UnitOfTime from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import DiscoveryInfoType -from . import wp -from .const import DOMAIN - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the wemportal component.""" - hass.data.setdefault(DOMAIN, {}) - return True +# from time import gmtime, strftime +from .const import TYPES +from .hpconst import MODBUS_SYS_ITEMS +from .entities import BuildEntityList async def async_setup_entry( hass: HomeAssistant, + # config: ConfigType, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, - discovery_info=None, + discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up Numbers.""" - hass.data.setdefault(DOMAIN, {}) - # hub = hass.data[DOMAIN][config_entry.entry_id] - host = config_entry.data[CONF_HOST] - port = config_entry.data[CONF_PORT] - # port = config_entry.data.get[CONF_PORT] - # host = "10.10.1.225" - # port = "502" + """Set up the sensor platform.""" + Entries = [] + async_add_entities( - [ - WW_Normal(host, port), - WW_Absenk(host, port), - WW_Push(host, port), - HK_Party(host, port), - HK_Pause(host, port), - HK_Raum_Soll_Komfort(host, port), - HK_Raum_Soll_Normal(host, port), - HK_Raum_Soll_Absenk(host, port), - HK_Heizkennlinie(host, port), - HK_SommerWinterUmschaltung(host, port), - HK_Heizen_Konstanttemperatur(host, port), - HK_Heizen_Konstanttemperatur_Absenk(host, port), - HK_Kuehlen_Konstanttemperatur(host, port), - ], + BuildEntityList(Entries, config_entry, MODBUS_SYS_ITEMS, TYPES.NUMBER), update_before_add=True, ) - - -class WW_Normal(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "WW Normal" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 40 - _attr_native_max_value = 60 - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Normal - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.WW_Normal = int(value) - - self._attr_native_value = whp.WW_Normal - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.WW_Normal - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Warmwasser")}, - } - - -class WW_Absenk(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "WW Absenk" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 30 - _attr_native_max_value = 40 - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Absenk - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.WW_Absenk = int(value) - - self._attr_native_value = whp.WW_Absenk - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.WW_Absenk - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Warmwasser")}, - } - - -class HK_Party(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Party" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 0 - _attr_native_max_value = 12 - _attr_native_step = 0.5 - _attr_native_unit_of_measurement = UnitOfTime.HOURS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # party_pause = whp.HK_Pause_Party - # if party_pause > 25: - # self._attr_native_value = (party_pause - 25) * 0.5 - # else: - # self._attr_native_value = 0 - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.HK_Pause_Party = int(25 + (value / 0.5)) - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - party_pause = whp.HK_Pause_Party - if party_pause > 25: - self._attr_native_value = (party_pause - 25) * 0.5 - else: - self._attr_native_value = 0 - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_Pause(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Pause" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 0 - _attr_native_max_value = 12 - _attr_native_step = 0.5 - _attr_native_unit_of_measurement = UnitOfTime.HOURS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # party_pause = whp.HK_Pause_Party - # if party_pause < 25: - # self._attr_native_value = (25 - party_pause) * 0.5 - # else: - # self._attr_native_value = 0 - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.HK_Pause_Party = int(25 - (value / 0.5)) - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - party_pause = whp.HK_Pause_Party - if party_pause < 25: - self._attr_native_value = (25 - party_pause) * 0.5 - else: - self._attr_native_value = 0 - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_Raum_Soll_Komfort(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Raumsollwert Komfort" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 20 - _attr_native_max_value = 30 - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Absenk - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.HK_RaumSoll_Komfort = int(value) - self._attr_native_value = whp.HK_RaumSoll_Komfort - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_RaumSoll_Komfort - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_Raum_Soll_Normal(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Raumsollwert Normal" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 15 - _attr_native_max_value = 25 - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Absenk - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.HK_RaumSoll_Normal = int(value) - self._attr_native_value = whp.HK_RaumSoll_Normal - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_RaumSoll_Normal - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_Raum_Soll_Absenk(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Raumsollwert Absenk" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 10 - _attr_native_max_value = 20 - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Absenk - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.HK_RaumSoll_Absenk = int(value) - self._attr_native_value = whp.HK_RaumSoll_Absenk - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_RaumSoll_Absenk - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_Heizkennlinie(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Heizkennlinie" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 0 - _attr_native_max_value = 1.5 - _attr_native_step = 0.05 - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Absenk - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.HK_Heizkennlinie = value - self._attr_native_value = whp.HK_Heizkennlinie - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_Heizkennlinie - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_SommerWinterUmschaltung(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Sommer Winter Umschaltung" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 3 - _attr_native_max_value = 30 - _attr_native_step = 0.5 - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Absenk - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.HK_SommerWinterUmschaltung = value - self._attr_native_value = whp.HK_SommerWinterUmschaltung - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_SommerWinterUmschaltung - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_Heizen_Konstanttemperatur(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Heizen Konstanttemperatur" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 20 - _attr_native_max_value = 45 - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Absenk - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.HK_HeizenKonstanttemperatur = int(value) - self._attr_native_value = whp.HK_HeizenKonstanttemperatur - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_HeizenKonstanttemperatur - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_Heizen_Konstanttemperatur_Absenk(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Heizen Konstanttemperatur Absenk" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 20 - _attr_native_max_value = 30 - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Absenk - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.HK_HeizenKonstanttemperaturAbsenk = int(value) - self._attr_native_value = whp.HK_HeizenKonstanttemperaturAbsenk - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_HeizenKonstanttemperaturAbsenk - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_Kuehlen_Konstanttemperatur(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Kühlen Konstanttemperatur" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 15 - _attr_native_max_value = 25 - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Absenk - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.HK_KuehlenKonstanttemperatur = int(value) - self._attr_native_value = whp.HK_KuehlenKonstanttemperatur - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_KuehlenKonstanttemperatur - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class WW_Push(NumberEntity): - """Representation of a WEM Portal number.""" - - _attr_name = "Push" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_value = 0 - _attr_should_poll = True - _attr_native_min_value = 0 - _attr_native_max_value = 240 - _attr_native_unit_of_measurement = UnitOfTime.MINUTES - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - # whp = wp.heat_pump(host, port) - # whp.connect() - # self._attr_native_value = whp.WW_Absenk - # self.async_write_ha_state() - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - whp.WW_Push = int(value) - - self._attr_native_value = whp.WW_Push - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.WW_Push - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Warmwasser")}, - } diff --git a/custom_components/weishaupt_modbus/select.py b/custom_components/weishaupt_modbus/select.py index fbf7970..da70ca8 100644 --- a/custom_components/weishaupt_modbus/select.py +++ b/custom_components/weishaupt_modbus/select.py @@ -1,14 +1,10 @@ -"""Select platform for wemportal component.""" - -from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import wp -from .const import DOMAIN +from .const import TYPES +from .hpconst import MODBUS_SYS_ITEMS +from .entities import BuildEntityList async def async_setup_entry( @@ -17,189 +13,9 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Select entry setup.""" - host = config_entry.data[CONF_HOST] - port = config_entry.data[CONF_PORT] + Entries = [] + async_add_entities( - [ - Sys_Betriebsart(host, port), - HK_Konfiguration(host, port), - HK_Anforderung_Typ(host, port), - HK_Betriebsart(host, port), - ], + BuildEntityList(Entries, config_entry, MODBUS_SYS_ITEMS, TYPES.SELECT), update_before_add=True, ) - - -##################### -# System # -##################### -class Sys_Betriebsart(SelectEntity): - """Representation of a WEM Portal Sensor.""" - - _attr_name = "Systembetriebsart" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - options = ["AUTOMATIK", "HEIZEN", "KÜHLEN", "SOMMER", "STANDBY", "2.WEZ", "FEHLER"] - _attr_current_option = "FEHLER" - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self.async_internal_will_remove_from_hass_port = port - - async def async_select_option(self, option: str) -> None: - """Call the API to change the parameter value.""" - - self._attr_current_option = option - whp = wp.heat_pump("10.10.1.225", 502) - whp.connect() - whp.Sys_Betriebsart = option - self.async_write_ha_state() - - async def async_update(self) -> None: - """Update Entity Only used by the generic entity update service.""" - # await self.coordinator.async_request_refresh() - whp = wp.heat_pump("10.10.1.225", 502) - whp.connect() - self._attr_current_option = whp.Sys_Betriebsart - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "System")}, - } - - -##################### -# Heizkreis # -##################### - - -class HK_Konfiguration(SelectEntity): - """Representation of a WEM Portal Sensor.""" - - _attr_name = "Konfiguration" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - options = [ - "AUS", - "PUMPENKREIS", - "MISCHKREIS", - "SOLLWERT (PUMPE M1)", - "FEHLER", - ] - _attr_current_option = "FEHLER" - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_select_option(self, option: str) -> None: - """Call the API to change the parameter value.""" - - self._attr_current_option = option - - self.async_write_ha_state() - - async def async_update( - self, - ) -> None: - """Update Entity Only used by the generic entity update service.""" - # await self.coordinator.async_request_refresh() - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_current_option = whp.HK_Konfiguration - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_Anforderung_Typ(SelectEntity): - """Representation of a WEM Portal Sensor.""" - - _attr_name = "Anforderung Typ" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - options = [ - "AUS", - "WITTERUNGSGEFÜHRT", - "KONSTANT", - ] - _attr_current_option = "" - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_select_option(self, option: str) -> None: - """Call the API to change the parameter value.""" - - self._attr_current_option = option - whp = wp.heat_pump("10.10.1.225", 502) - whp.connect() - whp.HK_AnforderungTyp = option - self.async_write_ha_state() - - async def async_update( - self, - ) -> None: - """Update Entity Only used by the generic entity update service.""" - # await self.coordinator.async_request_refresh() - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_current_option = whp.HK_AnforderungTyp - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_Betriebsart(SelectEntity): - """Representation of a WEM Portal Sensor.""" - - _attr_name = "Betriebsart" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - options = ["AUTOMATIK", "KOMFORT", "NORMAL", "ABSENKBETRIEB", "STANDBY"] - - _attr_current_option = "" - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_select_option(self, option: str) -> None: - """Call the API to change the parameter value.""" - - self._attr_current_option = option - whp = wp.heat_pump("10.10.1.225", 502) - whp.connect() - whp.HK_Betriebsart = option - self.async_write_ha_state() - - async def async_update( - self, - ) -> None: - """Update Entity Only used by the generic entity update service.""" - # await self.coordinator.async_request_refresh() - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_current_option = whp.HK_Betriebsart - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } diff --git a/custom_components/weishaupt_modbus/sensor.py b/custom_components/weishaupt_modbus/sensor.py index 5fb298d..ac820ce 100644 --- a/custom_components/weishaupt_modbus/sensor.py +++ b/custom_components/weishaupt_modbus/sensor.py @@ -1,24 +1,13 @@ -"""Platform for sensor integration.""" - from __future__ import annotations -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntity, - SensorStateClass, -) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT, UnitOfEnergy, UnitOfTemperature from homeassistant.core import HomeAssistant - -# from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import DiscoveryInfoType -# from time import gmtime, strftime -from . import wp -from .const import DOMAIN +from .const import TYPES +from .hpconst import MODBUS_SYS_ITEMS +from .entities import BuildEntityList async def async_setup_entry( @@ -29,812 +18,11 @@ async def async_setup_entry( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the sensor platform.""" - host = config_entry.data[CONF_HOST] - port = config_entry.data[CONF_PORT] + Entries = [] + + Entries = BuildEntityList(Entries, config_entry, MODBUS_SYS_ITEMS, TYPES.NUMBER_RO) + async_add_entities( - [ - Sys_Aussentemperatur1(host, port), - Sys_Aussentemperatur2(host, port), - Sys_Fehler(host, port), - Sys_Warnung(host, port), - Sys_Fehlerfrei(host, port), - Sys_Betriebsanzeige(host, port), - HK_RaumSollTemperatur(host, port), - HK_RaumTemperatur(host, port), - HK_RaumFeuchte(host, port), - HK_VorlaufSollTemperatur(host, port), - HK_VorlaufTemperatur(host, port), - sensor_measured_temp(host, port), - sensor_target_temp(host, port), - Energy_today(host, port), - Energy_yesterday(host, port), - Energy_month(host, port), - Energy_year(host, port), - HP_Betrieb(host, port), - HP_Stoermeldung(host, port), - HP_Leistungsanforderung(host, port), - Hp_Vorlauftemperatur(host, port), - Hp_Ruecklauftemperatur(host, port), - WW_Konfiguration(host, port), - ], + BuildEntityList(Entries, config_entry, MODBUS_SYS_ITEMS, TYPES.SENSOR), update_before_add=True, ) - - -##################### -# System # -##################### -class Sys_Aussentemperatur1(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Aussentemperatur1" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - # whp = wp.heat_pump(self._host, self._port) - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Sys_Aussentemperatur1 - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "System")}, - "name": "Wärmepumpe-System", - # "sw_version": "Device_SW_Version", - # "model": "Device_model", - "manufacturer": "Weishaupt", - } - - -class Sys_Aussentemperatur2(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Aussentemperatur2" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Sys_Aussentemperatur2 - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "System")}, - } - - -class Sys_Fehler(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Fehler" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Sys_Fehler - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "System")}, - } - - -class Sys_Warnung(SensorEntity): - """Representation of a Sensor.""" - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - _attr_name = "Warnung" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - # - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Sys_Warnung - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "System")}, - } - - -class Sys_Fehlerfrei(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Fehlerfrei" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - # - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Sys_Fehlerfrei - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "System")}, - } - - -class Sys_Betriebsanzeige(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Betriebsanzeige" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - # - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Sys_Betriebsanzeige - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "System")}, - } - - -##################### -# Heizkreis # -##################### -class HK_RaumSollTemperatur(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Raumsolltemperatur" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_Raumsolltemperatur - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - "name": "Wärmepumpe-Heizkreis", - # "sw_version": "Device_SW_Version", - # "model": "Device_model", - "manufacturer": "Weishaupt", - } - - -class HK_RaumTemperatur(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Raumtemperatur" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_Raumtemperatur - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_RaumFeuchte(SensorEntity): - """Representation of a Sensor.""" - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - _attr_name = "Raumfeuchte" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = "%" - _attr_device_class = SensorDeviceClass.MOISTURE - _attr_state_class = SensorStateClass.MEASUREMENT - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_Raumfeuchte - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_VorlaufSollTemperatur(SensorEntity): - """Representation of a Sensor.""" - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - _attr_name = "VorlaufSollTemperatur" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_Vorlaufsolltemperatur - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -class HK_VorlaufTemperatur(SensorEntity): - """Representation of a Sensor.""" - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - _attr_name = "VorlaufTemperatur" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.HK_Vorlauftemperatur - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Heizkreis")}, - } - - -##################### -# Warmwasser # -##################### - - -class sensor_measured_temp(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Ist Temperatur" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.WW_Ist - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Warmwasser")}, - "name": "Wärmepumpe-Warmwasser", - "manufacturer": "Weishaupt", - } - - -class sensor_target_temp(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Soll Temperatur" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.WW_Soll - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Warmwasser")}, - } - - -class WW_Konfiguration(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Konfiguration" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.WW_Konfiguration - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Warmwasser")}, - } - - ##################### - # Heatpump # - ##################### - - -class HP_Betrieb(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Betrieb" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Hp_Betrieb - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Wärmepumpe")}, - "name": "Wärmepumpe-Wärmepumpe", - "manufacturer": "Weishaupt", - } - - -class HP_Stoermeldung(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Störmeldung" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Hp_Stoermeldung - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Wärmepumpe")}, - } - - -class HP_Leistungsanforderung(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Leistungsanforderung" - _attr_unique_id = DOMAIN + _attr_name - _attr_native_unit_of_measurement = "%" - _attr_should_poll = True - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Hp_Leistungsanforderung - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Wärmepumpe")}, - } - - -class Hp_Vorlauftemperatur(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Vorlauftemperatur" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Hp_Vorlauftemperatur - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Wärmepumpe")}, - } - - -class Hp_Ruecklauftemperatur(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Rücklauftemperatur" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_state_class = SensorStateClass.MEASUREMENT - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Hp_Ruecklauftemperatur - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Wärmepumpe")}, - } - - -##################### -# 2. WEZ # -##################### - -##################### -# Eingänge # -##################### - - -##################### -# Statistik # -##################### - - -class Energy_today(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Energie heute" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR - _attr_device_class = SensorDeviceClass.ENERGY - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Energy_total_today - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Statistik")}, - "name": "Wärmepumpe-Statistik", - "manufacturer": "Weishaupt", - } - - -class Energy_yesterday(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Energie gestern" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR - _attr_device_class = SensorDeviceClass.ENERGY - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Energy_total_yesterday - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Statistik")}, - } - - -class Energy_month(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Energie Monat" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR - _attr_device_class = SensorDeviceClass.ENERGY - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Energy_total_month - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Statistik")}, - } - - -class Energy_year(SensorEntity): - """Representation of a Sensor.""" - - _attr_name = "Energie Jahr" - _attr_unique_id = DOMAIN + _attr_name - _attr_should_poll = True - _attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR - _attr_device_class = SensorDeviceClass.ENERGY - - def __init__(self, host, port) -> None: - """Init.""" - self._host = host - self._port = port - - async def async_update(self) -> None: - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - - whp = wp.heat_pump(self._host, self._port) - whp.connect() - self._attr_native_value = whp.Energy_total_year - - @property - def device_info(self) -> DeviceInfo: - """Information about this entity/device.""" - return { - "identifiers": {(DOMAIN, "Statistik")}, - } diff --git a/hacs.json b/hacs.json index 57ae91e..b857620 100644 --- a/hacs.json +++ b/hacs.json @@ -1,3 +1,3 @@ { - "name": "Weishaupt Modbus" + "name": "Weishaupt WBB" } \ No newline at end of file