From cb57dc1e92cd7112bb0b645207a0f7ae7a0fc0fe Mon Sep 17 00:00:00 2001 From: Constantin Pascal Date: Mon, 2 Feb 2026 14:30:44 +0200 Subject: [PATCH] Adding system status for ventilation modes --- .../rdz_hmi_control/binary_sensor.py | 65 +++++++++++++++++++ custom_components/rdz_hmi_control/const.py | 10 +++ .../rdz_hmi_control/coordinator.py | 12 ++++ .../rdz_hmi_control/modbus_client.py | 16 +++++ 4 files changed, 103 insertions(+) diff --git a/custom_components/rdz_hmi_control/binary_sensor.py b/custom_components/rdz_hmi_control/binary_sensor.py index c378681..e7b42f4 100644 --- a/custom_components/rdz_hmi_control/binary_sensor.py +++ b/custom_components/rdz_hmi_control/binary_sensor.py @@ -24,6 +24,10 @@ from .const import ( DATA_INTEGRATION_REQUEST, DATA_PUMP_ACTIVE, DATA_RENEWAL_REQUEST, + DATA_VENTILATION_MODE_DEHUMIDIFICATION, + DATA_VENTILATION_MODE_INTEGRATION, + DATA_VENTILATION_MODE_RENEWAL, + DATA_VENTILATION_MODE_VENTILATION, DATA_VENTILATION_REQUEST, DATA_ZONE_ACTIVITY, DOMAIN, @@ -42,6 +46,14 @@ ZONE_REQUEST_TYPES: dict[str, tuple[str, str]] = { "dehumidification_pump": (DATA_DEHUMIDIFICATION_PUMP, "Dehumidification pump"), } +# Ventilation mode types with their data keys and display names +VENTILATION_MODE_TYPES: dict[str, tuple[str, str]] = { + "dehumidification": (DATA_VENTILATION_MODE_DEHUMIDIFICATION, "Dehumidification"), + "ventilation": (DATA_VENTILATION_MODE_VENTILATION, "Ventilation"), + "renewal": (DATA_VENTILATION_MODE_RENEWAL, "Renewal"), + "integration": (DATA_VENTILATION_MODE_INTEGRATION, "Integration"), +} + async def async_setup_entry( hass: HomeAssistant, @@ -58,6 +70,10 @@ async def async_setup_entry( for pump_id in range(1, 9): entities.append(RDZPumpActiveBinarySensor(coordinator, pump_id)) + # Add ventilation mode binary sensors + for mode_type in VENTILATION_MODE_TYPES: + entities.append(RDZVentilationModeBinarySensor(coordinator, mode_type)) + # Add zone pump binary sensors for each configured zone for zone_id_str, zone_data in zones_config.items(): zone_id = int(zone_id_str) if isinstance(zone_id_str, str) else zone_id_str @@ -119,6 +135,55 @@ class RDZPumpActiveBinarySensor(CoordinatorEntity[RDZDataUpdateCoordinator], Bin self.async_write_ha_state() +class RDZVentilationModeBinarySensor(CoordinatorEntity[RDZDataUpdateCoordinator], BinarySensorEntity): + """Representation of an RDZ HMI ventilation mode binary sensor. + + Indicates whether a ventilation unit work mode is active. + """ + + _attr_has_entity_name = True + _attr_device_class = BinarySensorDeviceClass.RUNNING + + def __init__( + self, + coordinator: RDZDataUpdateCoordinator, + mode_type: str, + ) -> None: + """Initialize the ventilation mode binary sensor entity. + + Args: + coordinator: The data coordinator. + mode_type: The type of ventilation mode (key in VENTILATION_MODE_TYPES). + """ + super().__init__(coordinator) + self._mode_type = mode_type + self._data_key, self._display_name = VENTILATION_MODE_TYPES[mode_type] + + self._attr_unique_id = f"{DOMAIN}_{coordinator.client.host}_ventilation_mode_{mode_type}" + self._attr_name = self._display_name + + # Place on the System device + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, f"{coordinator.client.host}_system")}, + name="RDZ HMI System", + manufacturer="RDZ", + model="HMI Control System", + sw_version="1.0", + ) + + @property + def is_on(self) -> bool | None: + """Return True if the ventilation mode is active.""" + if self.coordinator.data is None: + return None + return self.coordinator.data.get(self._data_key) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self.async_write_ha_state() + + class RDZZonePumpBinarySensor(CoordinatorEntity[RDZDataUpdateCoordinator], BinarySensorEntity): """Representation of an RDZ HMI zone pump binary sensor. diff --git a/custom_components/rdz_hmi_control/const.py b/custom_components/rdz_hmi_control/const.py index cfd211c..4e48b2f 100644 --- a/custom_components/rdz_hmi_control/const.py +++ b/custom_components/rdz_hmi_control/const.py @@ -140,6 +140,12 @@ REGISTER_CALCULATED_WATER_TEMP_COUNT = 8 # Pump active bitmask register REGISTER_PUMP_ACTIVE = 7615 +# Ventilation mode registers (LSB = on/off) +REGISTER_VENTILATION_MODE_DEHUMIDIFICATION = 8201 +REGISTER_VENTILATION_MODE_VENTILATION = 8202 +REGISTER_VENTILATION_MODE_RENEWAL = 8203 +REGISTER_VENTILATION_MODE_INTEGRATION = 8204 + # Data keys for new features DATA_OUTSIDE_TEMP = "outside_temperature" DATA_TIME_SETTINGS = "time_settings" @@ -148,3 +154,7 @@ DATA_DELIVERY_WATER_TEMPS = "delivery_water_temps" DATA_CALCULATED_WATER_TEMPS = "calculated_water_temps" DATA_PUMP_ACTIVE = "pump_active" DATA_CALCULATED_SETPOINTS = "calculated_setpoints" +DATA_VENTILATION_MODE_DEHUMIDIFICATION = "ventilation_mode_dehumidification" +DATA_VENTILATION_MODE_VENTILATION = "ventilation_mode_ventilation" +DATA_VENTILATION_MODE_RENEWAL = "ventilation_mode_renewal" +DATA_VENTILATION_MODE_INTEGRATION = "ventilation_mode_integration" diff --git a/custom_components/rdz_hmi_control/coordinator.py b/custom_components/rdz_hmi_control/coordinator.py index ae9caf3..34861dd 100644 --- a/custom_components/rdz_hmi_control/coordinator.py +++ b/custom_components/rdz_hmi_control/coordinator.py @@ -34,6 +34,10 @@ from .const import ( DATA_SYSTEM_ACTIVATION, DATA_TEMPERATURES, DATA_TIME_SETTINGS, + DATA_VENTILATION_MODE_DEHUMIDIFICATION, + DATA_VENTILATION_MODE_INTEGRATION, + DATA_VENTILATION_MODE_RENEWAL, + DATA_VENTILATION_MODE_VENTILATION, DATA_VENTILATION_REQUEST, DATA_WINTER_SETPOINTS, DATA_ZONE_ACTIVITY, @@ -107,6 +111,9 @@ class RDZDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): integration_request = await self.client.read_integration_request() dehumidification_pump = await self.client.read_dehumidification_pump() + # Read ventilation mode registers + ventilation_modes = await self.client.read_ventilation_modes() + if temperatures is None or winter_setpoints is None or summer_setpoints is None: raise UpdateFailed("Failed to read data from Modbus device") @@ -140,6 +147,11 @@ class RDZDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): DATA_RENEWAL_REQUEST: renewal_request, DATA_INTEGRATION_REQUEST: integration_request, DATA_DEHUMIDIFICATION_PUMP: dehumidification_pump, + # Ventilation mode states + DATA_VENTILATION_MODE_DEHUMIDIFICATION: ventilation_modes.get("dehumidification") if ventilation_modes else None, + DATA_VENTILATION_MODE_VENTILATION: ventilation_modes.get("ventilation") if ventilation_modes else None, + DATA_VENTILATION_MODE_RENEWAL: ventilation_modes.get("renewal") if ventilation_modes else None, + DATA_VENTILATION_MODE_INTEGRATION: ventilation_modes.get("integration") if ventilation_modes else None, } except Exception as ex: diff --git a/custom_components/rdz_hmi_control/modbus_client.py b/custom_components/rdz_hmi_control/modbus_client.py index dbbda81..539cd8f 100644 --- a/custom_components/rdz_hmi_control/modbus_client.py +++ b/custom_components/rdz_hmi_control/modbus_client.py @@ -558,3 +558,19 @@ class RDZModbusClient: return await self._read_zone_bitmask( REGISTER_DEHUMIDIFICATION_PUMP_START, REGISTER_DEHUMIDIFICATION_PUMP_COUNT ) + + async def read_ventilation_modes(self) -> dict[str, bool] | None: + """Read ventilation mode registers 8201-8204. + + Returns dict with keys: dehumidification, ventilation, renewal, integration. + LSB (bit 0) of each register indicates on/off state. + """ + registers = await self.read_registers(8201, 4) + if registers is None: + return None + return { + "dehumidification": bool(registers[0] & 1), + "ventilation": bool(registers[1] & 1), + "renewal": bool(registers[2] & 1), + "integration": bool(registers[3] & 1), + }