From 4d6ddbabcadb3ba8915adb69cd3759145a5b365a Mon Sep 17 00:00:00 2001 From: Tvalley71 <83084467+Tvalley71@users.noreply.github.com> Date: Sun, 28 Apr 2024 14:15:59 +0200 Subject: [PATCH 1/2] Included cover Included cover for the bypass damper. The open and close buttons toggles the current state of the manual bypass. --- custom_components/dantherm/__init__.py | 2 +- custom_components/dantherm/const.py | 14 +-- custom_components/dantherm/cover.py | 116 ++++++++++++++----------- 3 files changed, 74 insertions(+), 58 deletions(-) diff --git a/custom_components/dantherm/__init__.py b/custom_components/dantherm/__init__.py index fdb33cd..a510016 100644 --- a/custom_components/dantherm/__init__.py +++ b/custom_components/dantherm/__init__.py @@ -31,7 +31,7 @@ ) PLATFORMS = [ - "button", + # "button", # left out for now "cover", "number", "select", diff --git a/custom_components/dantherm/const.py b/custom_components/dantherm/const.py index 1bef6a9..e322d66 100644 --- a/custom_components/dantherm/const.py +++ b/custom_components/dantherm/const.py @@ -5,7 +5,11 @@ from typing import Any from homeassistant.components.button import ButtonEntityDescription -from homeassistant.components.cover import CoverDeviceClass, CoverEntityDescription +from homeassistant.components.cover import ( + CoverDeviceClass, + CoverEntityDescription, + CoverEntityFeature, +) from homeassistant.components.number import ( NumberDeviceClass, NumberEntityDescription, @@ -95,7 +99,9 @@ class DanthermButtonEntityDescription(ButtonEntityDescription): class DanthermCoverEntityDescription(CoverEntityDescription): """Dantherm Cover Entity Description.""" + supported_features: CoverEntityFeature | None = None data_setaddress: int | None = None + data_setinternal: str | None = None data_setclass: DataClass | None = None state_open: int = None state_close: int = None @@ -201,10 +207,8 @@ class DanthermSwitchEntityDescription(SwitchEntityDescription): COVER_TYPES: dict[str, list[DanthermCoverEntityDescription]] = { "bypass_damper": DanthermCoverEntityDescription( key="bypass_damper", - data_setaddress=168, - data_setclass=DataClass.UInt16, - state_open=0x0080, - state_close=0x8080, + supported_features=CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE, + data_setinternal="set_bypass_damper", data_address=198, data_class=DataClass.UInt16, state_opening=64, diff --git a/custom_components/dantherm/cover.py b/custom_components/dantherm/cover.py index 27513c8..bab4a8a 100644 --- a/custom_components/dantherm/cover.py +++ b/custom_components/dantherm/cover.py @@ -5,14 +5,7 @@ from typing import Any from homeassistant.components.cover import CoverEntity, CoverEntityFeature -from homeassistant.const import ( - STATE_CLOSED, - STATE_CLOSING, - STATE_OPEN, - STATE_OPENING, - STATE_UNAVAILABLE, - STATE_UNKNOWN, -) +from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING from homeassistant.core import HomeAssistant from .const import COVER_TYPES, DOMAIN, DanthermCoverEntityDescription @@ -49,46 +42,63 @@ def __init__( self.has_entity_name = True self.entity_description: DanthermCoverEntityDescription = description self._attr_supported_features = 0 - if description.state_open: - self._attr_supported_features |= CoverEntityFeature.OPEN - if description.state_close: - self._attr_supported_features |= CoverEntityFeature.CLOSE - if description.state_stop: - self._attr_supported_features |= CoverEntityFeature.STOP + if description.supported_features: + self._attr_supported_features = description.supported_features + else: + if description.state_open: + self._attr_supported_features |= CoverEntityFeature.OPEN + if description.state_close: + self._attr_supported_features |= CoverEntityFeature.CLOSE + if description.state_stop: + self._attr_supported_features |= CoverEntityFeature.STOP # states + self._attr_available = False self._attr_is_closed = False self._attr_is_closing = False self._attr_is_opening = False - self._attr_last_state: int = 0 - self._call_active = False async def async_open_cover(self, **kwargs: Any) -> None: """Open cover.""" - result = await self._device.write_holding_registers( - description=self.entity_description, - value=self.entity_description.state_open, - ) - self._attr_available = result is not None - self.async_update_ha_state(True) + + if self.entity_description.data_setinternal: + await getattr(self._device, self.entity_description.data_setinternal)( + CoverEntityFeature.OPEN + ) + else: + await self._device.write_holding_registers( + description=self.entity_description, + value=self.entity_description.state_open, + ) + # await self.async_update_ha_state(True) async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" - result = await self._device.write_holding_registers( - description=self.entity_description, - value=self.entity_description.state_close, - ) - self._attr_available = result is not None - self.async_update_ha_state(True) + + if self.entity_description.data_setinternal: + await getattr(self._device, self.entity_description.data_setinternal)( + CoverEntityFeature.CLOSE + ) + else: + await self._device.write_holding_registers( + description=self.entity_description, + value=self.entity_description.state_close, + ) + # await self.async_update_ha_state(True) async def async_stop_cover(self, **kwargs: Any) -> None: """Stop cover.""" - result = await self._device.write_holding_registers( - description=self.entity_description, - value=self.entity_description.state_stop, - ) - self._attr_available = result is not None - self.async_update_ha_state(True) + + if self.entity_description.data_setinternal: + await getattr(self._device, self.entity_description.data_setinternal)( + CoverEntityFeature.STOP + ) + else: + await self._device.write_holding_registers( + description=self.entity_description, + value=self.entity_description.state_stop, + ) + # await self.async_update_ha_state(True) @property def native_value(self): @@ -98,28 +108,30 @@ def native_value(self): async def async_update(self, now: datetime | None = None) -> None: """Update the state of the cover.""" - self._attr_last_state = STATE_UNKNOWN result = await self._device.read_holding_registers( description=self.entity_description ) if result is None: self._attr_available = False - elif result == self.entity_description.state_closed: - self._attr_state = STATE_CLOSED - self._attr_is_closed = True - self._attr_is_closing = False - self._attr_is_opening = False - elif result == self.entity_description.state_closing: - self._attr_state = STATE_CLOSING - self._attr_is_closing = True - elif result == self.entity_description.state_opening: - self._attr_state = STATE_OPENING - self._attr_is_opening = True - elif result == self.entity_description.state_open: - self._attr_state = STATE_OPEN - self._attr_is_closed = False - self._attr_is_closing = False - self._attr_is_opening = False else: - self._attr_state = STATE_UNAVAILABLE + self._attr_available = True + + if result == self.entity_description.state_closed: + self._attr_state = STATE_CLOSED + self._attr_is_closed = True + self._attr_is_closing = False + self._attr_is_opening = False + elif result == self.entity_description.state_closing: + self._attr_state = STATE_CLOSING + self._attr_is_closing = True + self._attr_is_opening = False + elif result == self.entity_description.state_opening: + self._attr_state = STATE_OPENING + self._attr_is_opening = True + self._attr_is_closing = False + elif result == self.entity_description.state_opened: + self._attr_state = STATE_OPEN + self._attr_is_closed = False + self._attr_is_closing = False + self._attr_is_opening = False From 25dc32ba9e0b2209a5484a9fb7684cef759aa6ee Mon Sep 17 00:00:00 2001 From: Tvalley71 <83084467+Tvalley71@users.noreply.github.com> Date: Sun, 28 Apr 2024 14:16:26 +0200 Subject: [PATCH 2/2] May have fixed restart HA issue --- custom_components/dantherm/device.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/custom_components/dantherm/device.py b/custom_components/dantherm/device.py index 2263b78..70e1336 100644 --- a/custom_components/dantherm/device.py +++ b/custom_components/dantherm/device.py @@ -7,6 +7,7 @@ from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadBuilder, BinaryPayloadDecoder +from homeassistant.components.cover import CoverEntityFeature from homeassistant.components.modbus import modbus from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity, EntityDescription @@ -134,7 +135,11 @@ async def setup(self): _LOGGER.error("Modbus setup was unsuccessful") raise ValueError("Modbus setup was unsuccessful") - self._device_installed_components = await self._read_holding_uint16(610) + result = await self._read_holding_uint16(610) + if result is None: + raise ValueError("Dantherm unit probably not responding") + + self._device_installed_components = result _LOGGER.debug( "Installed components (610) = %s", hex(self._device_installed_components), @@ -304,6 +309,14 @@ async def set_fan_level(self, value): await self._write_holding_uint32(324, value) + async def set_bypass_damper(self, feature: CoverEntityFeature = None): + """Set bypass damper.""" + + if self.get_active_unit_mode & 0x80 == 0x80: + await self.set_active_unit_mode(0x8080) + else: + await self.set_active_unit_mode(0x80) + async def read_holding_registers( self, description: EntityDescription | None = None, @@ -421,7 +434,7 @@ async def _read_holding_registers(self, address, count): self._unit_id, address, count, "holding" ) if result is None: - _LOGGER.log( + _LOGGER.error( "Error reading holding register=%s count=%s", str(address), str(count) ) return result @@ -436,7 +449,7 @@ async def _write_holding_registers(self, address, values: list[int] | int): "write_registers", ) if result is None: - _LOGGER.log( + _LOGGER.error( "Error writing holding register=%s values=%s", str(address), str(values) ) @@ -495,6 +508,8 @@ async def _read_holding_uint16(self, address): """Read holding uint16 registers.""" result = await self._read_holding_registers(address, 1) + if result is None: + return None decoder = BinaryPayloadDecoder.fromRegisters( result.registers, byteorder=Endian.BIG, wordorder=Endian.LITTLE )