diff --git a/binary_sensor.py b/binary_sensor.py index 3c4896a..b583d8b 100644 --- a/binary_sensor.py +++ b/binary_sensor.py @@ -9,7 +9,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import ModbusRTUMonitorConfigEntry -from .const import COIL_COUNT, DOMAIN, MANUFACTURER, MODEL +from .const import COIL_COUNT, DOMAIN, HEATING_STATUS_REGISTER, MANUFACTURER, MODEL from .coordinator import ModbusRTUMonitorCoordinator @@ -22,24 +22,27 @@ async def async_setup_entry( coordinator = entry.runtime_data # Track which slaves have binary sensor entities - added_slaves: set[int] = set() + added_slaves_coils: set[int] = set() + added_slaves_zone_pump: set[int] = set() @callback def _async_add_binary_sensor_entities() -> None: - """Add binary sensor entities for newly discovered slaves with coil data.""" + """Add binary sensor entities for newly discovered slaves.""" if coordinator.data is None: return - current_slaves = { + entities = [] + + # Add coil binary sensors for slaves with coil data + current_slaves_coils = { slave_id for slave_id, slave_data in coordinator.data.items() if slave_data.coils is not None } - new_slaves = current_slaves - added_slaves + new_slaves_coils = current_slaves_coils - added_slaves_coils - if new_slaves: - entities = [] - for slave_id in new_slaves: + if new_slaves_coils: + for slave_id in new_slaves_coils: # Create 40 binary sensors (coil1-40) for each slave for coil_idx in range(COIL_COUNT): entities.append( @@ -47,8 +50,26 @@ async def async_setup_entry( coordinator, slave_id, coil_idx + 1 ) ) + added_slaves_coils.update(new_slaves_coils) + + # Add zone pump binary sensor for slaves with register data + current_slaves_zone_pump = { + slave_id + for slave_id, slave_data in coordinator.data.items() + if slave_data.registers is not None + and HEATING_STATUS_REGISTER in slave_data.registers + } + new_slaves_zone_pump = current_slaves_zone_pump - added_slaves_zone_pump + + if new_slaves_zone_pump: + for slave_id in new_slaves_zone_pump: + entities.append( + ModbusRTUMonitorZonePumpSensor(coordinator, slave_id) + ) + added_slaves_zone_pump.update(new_slaves_zone_pump) + + if entities: async_add_entities(entities) - added_slaves.update(new_slaves) # Subscribe to coordinator updates entry.async_on_unload( @@ -116,4 +137,66 @@ class ModbusRTUMonitorCoilSensor( @property def extra_state_attributes(self) -> dict[str, int]: """Return additional state attributes.""" - return {"coil_number": self._coil_number} \ No newline at end of file + return {"coil_number": self._coil_number} + + +class ModbusRTUMonitorZonePumpSensor( + CoordinatorEntity[ModbusRTUMonitorCoordinator], BinarySensorEntity +): + """Binary sensor entity for zone pump status.""" + + _attr_has_entity_name = True + _attr_name = "Zone pump" # User-friendly name + + def __init__( + self, + coordinator: ModbusRTUMonitorCoordinator, + slave_id: int, + ) -> None: + """Initialize zone pump binary sensor.""" + super().__init__(coordinator) + + self._slave_id = slave_id + + self._attr_unique_id = ( + f"{coordinator.config_entry.entry_id}_{slave_id}_zone_pump" + ) + + # Device info (groups with climate and humidity sensor) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, f"{coordinator.config_entry.entry_id}_{slave_id}")}, + name=f"Modbus Slave {slave_id}", + manufacturer=MANUFACTURER, + model=MODEL, + ) + + @property + def available(self) -> bool: + """Return if entity is available.""" + if self._slave_id not in self.coordinator.data: + return False + slave_data = self.coordinator.data[self._slave_id] + if not slave_data.available or slave_data.registers is None: + return False + return HEATING_STATUS_REGISTER in slave_data.registers + + @property + def is_on(self) -> bool | None: + """Return true if the zone pump is on (register value = 1).""" + if self._slave_id not in self.coordinator.data: + return None + slave_data = self.coordinator.data[self._slave_id] + if slave_data.registers is None: + return None + + zone_pump_value = slave_data.registers.get(HEATING_STATUS_REGISTER) + if zone_pump_value is None: + return None + + # ON if register value is 1, OFF otherwise + return zone_pump_value == 1 + + @property + def extra_state_attributes(self) -> dict[str, int]: + """Return additional state attributes.""" + return {"register_address": HEATING_STATUS_REGISTER} \ No newline at end of file diff --git a/const.py b/const.py index efe1bbc..0d78da4 100644 --- a/const.py +++ b/const.py @@ -31,7 +31,7 @@ REGISTER_NAMES: dict[int, str] = { 156: "Day of week", 157: "Current temperature", 179: "Outside temperature", - 211: "Heating status", + 211: "Zone pump", # Add more registers as discovered # Example: # 165: "Register name", diff --git a/hub.py b/hub.py index 6f1d21a..9b6466f 100644 --- a/hub.py +++ b/hub.py @@ -226,8 +226,8 @@ class ModbusRTUMonitorHub: self._writer.close() await self._writer.wait_closed() - # Wait before reconnecting - await asyncio.sleep(5) + # Wait before reconnecting (30 seconds to prevent DDOS during server outages) + await asyncio.sleep(30) # Attempt reconnection try: diff --git a/sensor.py b/sensor.py index af7a356..3ccd17e 100644 --- a/sensor.py +++ b/sensor.py @@ -16,6 +16,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import ModbusRTUMonitorConfigEntry from .const import ( DOMAIN, + HEATING_STATUS_REGISTER, MANUFACTURER, MODEL, REGISTER_MONITOR_COUNT,