diff --git a/custom_components/weishaupt_modbus b/custom_components/weishaupt_modbus deleted file mode 160000 index 870804e..0000000 --- a/custom_components/weishaupt_modbus +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 870804e8bbaa1d7995fa730c7515bf6f9c19f08a diff --git a/custom_components/weishaupt_modbus/__init__.py b/custom_components/weishaupt_modbus/__init__.py new file mode 100644 index 0000000..648d96c --- /dev/null +++ b/custom_components/weishaupt_modbus/__init__.py @@ -0,0 +1,34 @@ +"""Weishaupt Modbus Integration.""" + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN + +PLATFORMS: list[str] = ["number", "select", "sensor"] + + +# 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"]) + + # This creates each HA object for each platform your device requires. + # It's done by calling the `async_setup_entry` function in each platform module. + 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) + + return unload_ok diff --git a/custom_components/weishaupt_modbus/__pycache__/__init__.cpython-312.pyc b/custom_components/weishaupt_modbus/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..052d363 Binary files /dev/null and b/custom_components/weishaupt_modbus/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_components/weishaupt_modbus/__pycache__/config_flow.cpython-312.pyc b/custom_components/weishaupt_modbus/__pycache__/config_flow.cpython-312.pyc new file mode 100644 index 0000000..bf5e7a4 Binary files /dev/null and b/custom_components/weishaupt_modbus/__pycache__/config_flow.cpython-312.pyc differ diff --git a/custom_components/weishaupt_modbus/__pycache__/const.cpython-312.pyc b/custom_components/weishaupt_modbus/__pycache__/const.cpython-312.pyc new file mode 100644 index 0000000..179d2f6 Binary files /dev/null and b/custom_components/weishaupt_modbus/__pycache__/const.cpython-312.pyc differ diff --git a/custom_components/weishaupt_modbus/__pycache__/number.cpython-312.pyc b/custom_components/weishaupt_modbus/__pycache__/number.cpython-312.pyc new file mode 100644 index 0000000..2d32238 Binary files /dev/null and b/custom_components/weishaupt_modbus/__pycache__/number.cpython-312.pyc differ diff --git a/custom_components/weishaupt_modbus/__pycache__/select.cpython-312.pyc b/custom_components/weishaupt_modbus/__pycache__/select.cpython-312.pyc new file mode 100644 index 0000000..753a166 Binary files /dev/null and b/custom_components/weishaupt_modbus/__pycache__/select.cpython-312.pyc differ diff --git a/custom_components/weishaupt_modbus/__pycache__/sensor.cpython-312.pyc b/custom_components/weishaupt_modbus/__pycache__/sensor.cpython-312.pyc new file mode 100644 index 0000000..52ce307 Binary files /dev/null and b/custom_components/weishaupt_modbus/__pycache__/sensor.cpython-312.pyc differ diff --git a/custom_components/weishaupt_modbus/__pycache__/wp.cpython-312.pyc b/custom_components/weishaupt_modbus/__pycache__/wp.cpython-312.pyc new file mode 100644 index 0000000..727bfbd Binary files /dev/null and b/custom_components/weishaupt_modbus/__pycache__/wp.cpython-312.pyc differ diff --git a/custom_components/weishaupt_modbus/config_flow.py b/custom_components/weishaupt_modbus/config_flow.py new file mode 100644 index 0000000..01e0337 --- /dev/null +++ b/custom_components/weishaupt_modbus/config_flow.py @@ -0,0 +1,95 @@ +"""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 + +# 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 + # The exceptions are defined at the end of this file, and are used in the + # `async_step_user` method below. + 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( + # your_validate_func, data["username"], data["password"] + # ) + + # If you cannot connect: + # throw CannotConnect + # If the authentication is wrong: + # InvalidAuth + + # Return info that you want to store in the config entry. + # "Title" is what is displayed to the user for this hub device + # It is stored internally in HA as part of the device config. + # See `async_step_user` below for how this is used + return {"title": data["host"]} + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Hello World.""" + + 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 + # automatically. This example uses PUSH, as the dummy hub will notify HA of + # changes. + 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`), + # and when that has some validated input, it calls `async_create_entry` to + # actually create the HA config entry. Note the "title" value is returned by + # `validate_input` above. + errors = {} + if user_input is not None: + try: + info = await validate_input(self.hass, user_input) + + return self.async_create_entry(title=info["title"], data=user_input) + + except Exception: + 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. + 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 new file mode 100644 index 0000000..8e32865 --- /dev/null +++ b/custom_components/weishaupt_modbus/const.py @@ -0,0 +1,8 @@ +"""Constants.""" + +from datetime import timedelta + +DOMAIN = "weishaupt_modbus" +SCAN_INTERVAL = timedelta(minutes=1) +UNIQUE_ID = "unique_id" +APPID = 100 diff --git a/custom_components/weishaupt_modbus/manifest.json b/custom_components/weishaupt_modbus/manifest.json new file mode 100644 index 0000000..851b5ef --- /dev/null +++ b/custom_components/weishaupt_modbus/manifest.json @@ -0,0 +1,11 @@ +{ + "domain": "weishaupt_modbus", + "name": "Weishaupt Modbus Integration", + "codeowners": ["@MadOne"], + "config_flow": true, + "documentation": "https://www.not_yet.com", + "iot_class": "local_polling", + "requirements": [], + "version": "0.0.1" +} + diff --git a/custom_components/weishaupt_modbus/number.py b/custom_components/weishaupt_modbus/number.py new file mode 100644 index 0000000..a113716 --- /dev/null +++ b/custom_components/weishaupt_modbus/number.py @@ -0,0 +1,78 @@ +"""Number platform for wemportal component.""" + +from homeassistant.components.number import NumberEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_PORT, UnitOfTemperature +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 . 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 + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, + discovery_info=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" + async_add_entities([Number(host, port)], update_before_add=True) + + +class Number(NumberEntity): + """Representation of a WEM Portal number.""" + + _attr_name = "WW Soll Temp" + _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_Soll + # 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_Soll = int(value) + + self._attr_native_value = whp.WW_Soll + 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_Soll + + @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 new file mode 100644 index 0000000..bbfabc1 --- /dev/null +++ b/custom_components/weishaupt_modbus/select.py @@ -0,0 +1,108 @@ +"""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 + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Select entry setup.""" + host = config_entry.data[CONF_HOST] + port = config_entry.data[CONF_PORT] + async_add_entities( + [ + Sys_Betriebsart(host, port), + HK_Konfiguration(host, port), + ], + update_before_add=True, + ) + + +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 + + 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")}, + } + + +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")}, + } diff --git a/custom_components/weishaupt_modbus/sensor.py b/custom_components/weishaupt_modbus/sensor.py new file mode 100644 index 0000000..f80774c --- /dev/null +++ b/custom_components/weishaupt_modbus/sensor.py @@ -0,0 +1,809 @@ +"""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 + + +async def async_setup_entry( + hass: HomeAssistant, + # config: ConfigType, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, +) -> None: + """Set up the sensor platform.""" + host = config_entry.data[CONF_HOST] + port = config_entry.data[CONF_PORT] + 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), + ], + 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_info + + @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 = "Wärmepumpe 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 = "Wärmepumpe 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 = "Wärmepumpe 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 = "Wärmepumpe 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 = "Wärmepumpe 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 = "Energy Today" + _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, "Statistics")}, + "name": "Wärmepumpe-Statistics", + "manufacturer": "Weishaupt", + } + + +class Energy_yesterday(SensorEntity): + """Representation of a Sensor.""" + + _attr_name = "Energy yesterday" + _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, "Statistics")}, + } + + +class Energy_month(SensorEntity): + """Representation of a Sensor.""" + + _attr_name = "Energy month" + _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, "Statistics")}, + } + + +class Energy_year(SensorEntity): + """Representation of a Sensor.""" + + _attr_name = "Energy year" + _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, "Statistics")}, + } diff --git a/custom_components/weishaupt_modbus/strings.json b/custom_components/weishaupt_modbus/strings.json new file mode 100644 index 0000000..85a72ff --- /dev/null +++ b/custom_components/weishaupt_modbus/strings.json @@ -0,0 +1,32 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + } + } + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Account is already configured" + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Web scraping interval (default = 1800 sec)", + "api_scan_interval": "Api scan interval (default = 300 sec)", + "language": "Language (default = en)", + "mode": "Mode(default = api)" + } + } + } + } + } \ No newline at end of file diff --git a/custom_components/weishaupt_modbus/translations/en.json b/custom_components/weishaupt_modbus/translations/en.json new file mode 100644 index 0000000..386cd56 --- /dev/null +++ b/custom_components/weishaupt_modbus/translations/en.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "api_scan_interval": "Api scan interval (default = 300 sec)", + "language": "Language (default = en)", + "mode": "Mode(default = api)", + "scan_interval": "Web scraping interval (default = 1800 sec)" + } + } + } + } +} \ No newline at end of file diff --git a/custom_components/weishaupt_modbus/wp.py b/custom_components/weishaupt_modbus/wp.py new file mode 100644 index 0000000..50f6b2c --- /dev/null +++ b/custom_components/weishaupt_modbus/wp.py @@ -0,0 +1,502 @@ +"""Platform for sensor integration.""" + +from pymodbus.client import ModbusTcpClient as ModbusClient + +# import logging + +# hp_ip = "10.10.1.225" +# hp_port = 502 + +# logging.basicConfig() +# log = logging.getLogger() +# log.setLevel(logging.DEBUG) +APPID_offset = 100 + + +class heat_pump: + """Test.""" + + def __init__(self, hp_ip, hp_port) -> None: + """Test.""" + self._ip = hp_ip + self._port = hp_port + self.WWP = None + + def connect(self): + """Test.""" + try: + self.WWP = ModbusClient(host=self._ip, port=self._port) + return self.WWP.connected + except: + return None + + ############################################################################################################################## + # Modbus Register List: # + # https://docs.google.com/spreadsheets/d/1EZ3QgyB41xaXo4B5CfZe0Pi8KPwzIGzK/edit?gid=1730751621#gid=1730751621 # + ############################################################################################################################## + + ##################### + # System # + ##################### + @property + def Sys_Aussentemperatur1(self): + """Outer Temperature1.""" + try: + return self.WWP.read_input_registers(30001, slave=1).registers[0] / 10 + except: + return None + + @property + def Sys_Aussentemperatur2(self): + """Outer Temperature2.""" + try: + return self.WWP.read_input_registers(30002, slave=1).registers[0] / 10 + except: + return None + + @property + def Sys_Fehler(self): + """Outer Temperature2.""" + try: + val = self.WWP.read_input_registers(30004, slave=1).registers[0] + if val == 65535: + return "kein Fehler" + return "Fehler: " + val + except: + return None + + @property + def Sys_Warnung(self): + """Outer Temperature2.""" + try: + val = self.WWP.read_input_registers(30004, slave=1).registers[0] + if val == 65535: + return "kein Fehler" + return "Fehler: " + val + except: + return None + + @property + def Sys_Fehlerfrei(self): + """Outer Temperature2.""" + try: + val = self.WWP.read_input_registers(30005, slave=1).registers[0] + if val == 0: + return "Fehler aktiv" + return "Störungsfreier Betrieb" + except: + return None + + @property + def Sys_Betriebsanzeige(self): # noqa: C901 + """Energy used today.""" + try: + val = self.WWP.read_input_registers(30006, slave=1).registers[0] + match val: + case 0: + return "Undefiniert" + case 1: + return "Relaistest" + case 2: + return "Notaus" + case 3: + return "Diagnose" + case 4: + return "Handbetrieb" + case 5: + return "Handbetrieb Heizen" + case 6: + return "Handbetrieb Kühlen" + case 7: + return "Manueller Abtaubetrieb" + case 8: + return "Abtauen" + case 9: + return "WEZ2" + case 10: + return "EVU_SPERRE" + case 11: + return "SG Tarif" + case 12: + return "SG Maximal" + case 13: + return "Tarifladung" + case 14: + return "Erhöhter Betrieb" + case 15: + return "Standzeit" + case 16: + return "Standbybetrieb" + case 17: + return "Spülbetrieb" + case 18: + return "Frostschutz" + case 19: + return "Heizbetrieb" + case 20: + return "Warmwasserbetrieb" + case 21: + return "Legionellenschutz" + case 22: + return "Umschaltung HZ KU" + case 23: + return "Kühlbetrieb" + case 24: + return "Passive Kühlung" + case 25: + return "Sommerbetrieb" + case 26: + return "Schwimmbad" + case 27: + return "Urlaub" + case 28: + return "Estrich" + case 29: + return "Gesperrt" + case 30: + return "Sperre AT" + case 31: + return "Sperre Sommer" + case 32: + return "Sperre Winter" + case 33: + return "Einsatzgrenze" + case 34: + return "HK Sperre" + case 35: + return "Absenk" + except: + return None + + @property + def Sys_Betriebsart(self): + """Energy used today.""" + val = self.WWP.read_holding_registers(40001, slave=1).registers[0] + match val: + case 0: + return "AUTOMATIK" + case 1: + return "HEIZEN" + case 2: + return "KÜHLEN" + case 3: + return "SOMMER" + case 4: + return "STANDBY" + case 5: + return "2.WEZ" + + ##################### + # Heizkreis # + ##################### + @property + def HK_Raumsolltemperatur(self): + """Raumsolltemperatur.""" + return self.WWP.read_input_registers(31101, slave=1).registers[0] / 10 + + @property + def HK_Raumtemperatur(self): + """Raumtemperatur.""" + val = self.WWP.read_input_registers(31102, slave=1).registers[0] + if val == 32768: + return None + return val / 10 + + @property + def HK_Raumfeuchte(self): + """Raumtemperatur.""" + val = self.WWP.read_input_registers(31103, slave=1).registers[0] + if val == 65535: + return None + return val + + @property + def HK_Vorlaufsolltemperatur(self): + """HK_Vorlaufsolltemperatur.""" + return self.WWP.read_input_registers(31104, slave=1).registers[0] / 10 + + @property + def HK_Vorlauftemperatur(self): + """HK_Vorlauftemperatur.""" + val = self.WWP.read_input_registers(31105, slave=1).registers[0] + if val == 32768: + return None + return val / 10 + + @property + def HK_Konfiguration(self): + """Energy used today.""" + val = self.WWP.read_holding_registers(41101, slave=1).registers[0] + match val: + case 0: + return "AUS" + case 1: + return "PUMPENKREIS" + case 2: + return "MISCHKREIS" + case 3: + return "SOLLWERT (PUMPE M1)" + + @property + def HK_AnforderungTyp(self): + """Energy used today.""" + val = self.WWP.read_holding_registers(41102, slave=1).registers[0] + match val: + case 0: + return "AUS" + case 1: + return "WITTERUNGSGEFÜHRT" + case 2: + return "KONSTANT" + + @property + def HK_Betriebsart(self): + """Energy used today.""" + val = self.WWP.read_holding_registers(41103, slave=1).registers[0] + + match val: + case 0: + return "AUTOMATIK" + case 1: + return "KOMFORT" + case 2: + return "NORMAL" + case 3: + return "ABSENKBETRIEB" + case 5: + return "STANDBY" + + @property + def HK_Pause_Party(self): + """Energy used today.""" + val = self.WWP.read_holding_registers(41104, slave=1).registers[0] + if val == 25: + return "Automatik" + if val < 25: + time = (25 - val) * 0.5 + return "Pausenzeit " + time + "h" + if val > 25: + time = (val - 25) * 0.5 + return "Partyzeit " + val * 0.5 + "h" + + ##################### + # Warm Water # + ##################### + @property + def WW_Soll(self): + """Test.""" + return self.WWP.read_holding_registers(42103, slave=1).registers[0] / 10 + + @WW_Soll.setter + def WW_Soll(self, value): + self.WWP.write_register(42103, value * 10, slave=1) + + @property + def WW_Ist(self): + """Temperature of warm-water.""" + return self.WWP.read_input_registers(32102, slave=1).registers[0] / 10 + + @property + def WW_Soll_info(self): + """Temperature of warm-water.""" + return self.WWP.read_input_registers(32101, slave=1).registers[0] / 10 + + ##################### + # Heatpump # + ##################### + @property + def Hp_Betrieb(self): # noqa: C901 + """Energy used today.""" + val = self.WWP.read_input_registers(33101, slave=1).registers[0] + match val: + case 0: + return "Undefiniert" + case 1: + return "Relaistest" + case 2: + return "Notaus" + case 3: + return "Diagnose" + case 4: + return "Handbetrieb" + case 5: + return "Handbetrieb Heizen" + case 6: + return "Handbetrieb Kühlen" + case 7: + return "Manueller Abtaubetrieb" + case 8: + return "Abtauen" + case 9: + return "WEZ2" + case 10: + return "EVU_SPERRE" + case 11: + return "SG Tarif" + case 12: + return "SG Maximal" + case 13: + return "Tarifladung" + case 14: + return "Erhöhter Betrieb" + case 15: + return "Standzeit" + case 16: + return "Standbybetrieb" + case 17: + return "Spülbetrieb" + case 18: + return "Frostschutz" + case 19: + return "Heizbetrieb" + case 20: + return "Warmwasserbetrieb" + case 21: + return "Legionellenschutz" + case 22: + return "Umschaltung HZ KU" + case 23: + return "Kühlbetrieb" + case 24: + return "Passive Kühlung" + case 25: + return "Sommerbetrieb" + case 26: + return "Schwimmbad" + case 27: + return "Urlaub" + case 28: + return "Estrich" + case 29: + return "Gesperrt" + case 30: + return "Sperre AT" + case 31: + return "Sperre Sommer" + case 32: + return "Sperre Winter" + case 33: + return "Einsatzgrenze" + case 34: + return "HK Sperre" + case 35: + return "Absenk" + + @property + def Hp_Stoermeldung(self): + """Energy used today.""" + val = self.WWP.read_input_registers(33102, slave=1).registers[0] + match val: + case 0: + return "Störung" + case 1: + return "Störungsfrei" + + @property + def Hp_Leistungsanforderung(self): + """Energy used today.""" + return self.WWP.read_input_registers(33103, slave=1).registers[0] + + @property + def Hp_Vorlauftemperatur(self): + """Energy used today.""" + return self.WWP.read_input_registers(33104, slave=1).registers[0] / 10 + + @property + def Hp_Ruecklauftemperatur(self): + """Energy used today.""" + return self.WWP.read_input_registers(33105, slave=1).registers[0] / 10 + + ##################### + # Statistics # + ##################### + @property + def Energy_total_today(self): + """Energy used today.""" + return self.WWP.read_input_registers(36101, slave=1).registers[0] + + @property + def Energy_total_yesterday(self): + """Energy used yesterday.""" + return self.WWP.read_input_registers(36102, slave=1).registers[0] + + @property + def Energy_total_month(self): + """Energy used month.""" + return self.WWP.read_input_registers(36103, slave=1).registers[0] + + @property + def Energy_total_year(self): + """Energy used year.""" + return self.WWP.read_input_registers(36104, slave=1).registers[0] + + @property + def Heating_total_today(self): + """Energy used for heating today.""" + return self.WWP.read_input_registers(36201, slave=1).registers[0] + + @property + def Heating_total_yesterday(self): + """Energy used for heating yesterday.""" + return self.WWP.read_input_registers(36202, slave=1).registers[0] + + @property + def Heating_total_month(self): + """Energy used for heating month.""" + return self.WWP.read_input_registers(36203, slave=1).registers[0] + + @property + def Heating_total_year(self): + """Energy used for heating year.""" + return self.WWP.read_input_registers(36204, slave=1).registers[0] + + @property + def Water_total_today(self): + """Energy used for heating water today.""" + return self.WWP.read_input_registers(36301, slave=1).registers[0] + + @property + def Water_total_yesterday(self): + """Energy used for heating water yesterday.""" + return self.WWP.read_input_registers(36302, slave=1).registers[0] + + @property + def Water_total_month(self): + """Energy used for heating water month.""" + return self.WWP.read_input_registers(36303, slave=1).registers[0] + + @property + def Water_total_year(self): + """Energy used for heating water year.""" + return self.WWP.read_input_registers(36304, slave=1).registers[0] + + @property + def Cooling_total_today(self): + """Energy used for cooling.""" + return self.WWP.read_input_registers(36401, slave=1).registers[0] + + @property + def Cooling_total_yesterday(self): + """Energy used for cooling.""" + return self.WWP.read_input_registers(36402, slave=1).registers[0] + + @property + def Cooling_total_month(self): + """Energy used for cooling.""" + return self.WWP.read_input_registers(36403, slave=1).registers[0] + + @property + def Cooling_total_year(self): + """Energy used for cooling.""" + return self.WWP.read_input_registers(36404, slave=1).registers[0] + + +# whp = heat_pump(hp_ip, hp_port) +# whp.connect() +# print(whp.WW_Soll) +# whp.WW_Soll = 44 +# print(whp.WW_Soll) +# whp.WW_Soll = 45 +# print(whp.WW_Soll) + +# print(whp.WW_Ist)