Skip to content

Commit

Permalink
Added binary sensor for filter cleaning. Refactored status updating. …
Browse files Browse the repository at this point in the history
…Fixed an exception when unloading. Added icons for entities.
  • Loading branch information
martinstafford committed Sep 23, 2024
1 parent a08fab0 commit 8f9d0fd
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 46 deletions.
11 changes: 5 additions & 6 deletions custom_components/daikin_d3net/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
_LOGGER = logging.getLogger(__name__)


PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SENSOR]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand Down Expand Up @@ -52,8 +52,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if not unload_ok:
return False

coordinator: D3netCoordinator = entry
coordinator.gateway.close()
coordinator: D3netCoordinator = entry.runtime_data
await coordinator.gateway.async_close()

return True

Expand Down Expand Up @@ -100,6 +100,5 @@ def name(self, name):
self._name = name

async def _async_update_data(self):
"""Update Daikin Modbus data."""
for unit in self._gateway.units:
await unit.async_update()
"""Update the status of all units."""
await self._gateway.async_unit_status()
67 changes: 67 additions & 0 deletions custom_components/daikin_d3net/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from __future__ import annotations

import logging

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .__init__ import D3netCoordinator
from .climate import ACTION_DAIKIN_HA
from .d3net.gateway import D3netUnit

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Initialize all the Climate Entities."""
coordinator: D3netCoordinator = entry.runtime_data
entities = []
for unit in coordinator.gateway.units:
entities.append(D3netSensorFilter(coordinator, unit))
async_add_entities(entities)


class D3netSensorBase(CoordinatorEntity, BinarySensorEntity):
"""Consolidation of sensor initialization."""

def __init__(self, coordinator: D3netCoordinator, unit: D3netUnit) -> None:
"""Initialize the sensor object."""
super().__init__(coordinator, context=unit)
self._unit = unit
self._coordinator = coordinator
self._attr_device_info: DeviceInfo = coordinator.device_info(unit)
self._attr_device_name = self._attr_device_info["name"]

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.async_write_ha_state()


class D3netSensorFilter(D3netSensorBase):
"""Binary Sensor object for filter cleaning alter."""

def __init__(self, coordinator: D3netCoordinator, unit: D3netUnit) -> None:
"""Initialize custom properties for this sensor."""
super().__init__(coordinator, unit)
self._attr_name = self._attr_device_info["name"] + " Filter"
self._attr_unique_id = self._attr_name

@property
def is_on(self) -> bool:
"""State of the Clean Filter warning."""
return self._unit.status.filter_warning

@property
def icon(self) -> str:
"""Icon for filter warning."""
return "mdi:vacuum" if self._unit.status.filter_warning else "mdi:air-filter"
75 changes: 36 additions & 39 deletions custom_components/daikin_d3net/d3net/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ async def async_close(self):
async with self._lock:
await self._client.close()

async def _async_unit_status(self, unit):
"""Perform the unit update. Lock must already be held."""
await self._throttle_start()
response = await self._client.read_input_registers(
address=DecodeUnitStatus.ADDRESS + unit.index * DecodeUnitStatus.COUNT,
count=DecodeUnitStatus.COUNT,
slave=self._slave,
)
await self._throttle_end()
unit.status = DecodeUnitStatus(response.registers)

async def _async_unit_capabilities(self, unit):
"""Retrieve the unit capabilities."""
await self._throttle_start()
# _LOGGER.info("Determining capabilities for Unit %s", unit.unit_id)
response = await self._client.read_input_registers(
address=DecodeUnitCapability.ADDRESS
+ unit.index * DecodeUnitCapability.COUNT,
count=DecodeUnitCapability.COUNT,
slave=self._slave,
)
await self._throttle_end()
unit.capabilities = DecodeUnitCapability(response.registers)

async def async_setup(self):
"""Return a bool array of connected units."""
async with self._lock:
Expand All @@ -90,44 +114,21 @@ async def async_setup(self):
if connected:
unit = D3netUnit(self, index)
self._units.append(unit)
await self._async_unit_capabilities(unit)
await self._async_unit_status(unit)

await self._throttle_start()
# _LOGGER.info("Determining capabilities for Unit %s", unit.unit_id)
unit_capabilities = await self._client.read_input_registers(
address=DecodeUnitCapability.ADDRESS
+ index * DecodeUnitCapability.COUNT,
count=DecodeUnitCapability.COUNT,
slave=self._slave,
)
await self._throttle_end()
unit.capabilities = DecodeUnitCapability(
unit_capabilities.registers
)

await self._throttle_start()
# _LOGGER.info("Updating status for Unit %s", unit.unit_id)
unit_status = await self._client.read_input_registers(
address=DecodeUnitStatus.ADDRESS
+ index * DecodeUnitStatus.COUNT,
count=DecodeUnitStatus.COUNT,
slave=self._slave,
)
await self._throttle_end()
unit.status = DecodeUnitStatus(unit_status.registers)

async def async_unit_status(self, unit):
"""Fetch the status of a selected unit."""
async def async_unit_status(self, unit=None):
"""Update the status of the supplied unit, or all units if none supplied."""
async with self._lock:
await self._async_connect()
await self._throttle_start()
# _LOGGER.info("Updating status for Unit %s", unit.unit_id)
unit_status = await self._client.read_input_registers(
address=DecodeUnitStatus.ADDRESS + unit.index * DecodeUnitStatus.COUNT,
count=DecodeUnitStatus.COUNT,
slave=self._slave,
)
await self._throttle_end()
unit.status = DecodeUnitStatus(unit_status.registers)
for item in [unit] if unit else self._units:
await self._async_unit_status(item)

# async def async_unit_status(self, unit):
# """Fetch the status of a selected unit."""
# async with self._lock:
# await self._async_connect()
# await self._async_unit_status(unit)

async def async_write(self, address: int, values: list[int]):
"""Write a register."""
Expand Down Expand Up @@ -206,7 +207,3 @@ def writer(self):
def writer(self, writer: Writer):
"""Set the Writer object for the unit."""
self._writer = writer

async def async_update(self):
"""Update Unit data from Gateway."""
await self._gateway.async_unit_status(self)
2 changes: 1 addition & 1 deletion custom_components/daikin_d3net/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"documentation": "https://github.com/martinstafford/daikin_d3net",
"iot_class": "local_polling",
"requirements": ["pymodbus==3.6.9"],
"version": "1.1.0",
"version": "1.2.0",
"issue_tracker": "https://github.com/martinstafford/daikin_d3net/issues"
}
20 changes: 20 additions & 0 deletions custom_components/daikin_d3net/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .__init__ import D3netCoordinator
from .climate import ACTION_DAIKIN_HA
from .d3net.gateway import D3netUnit
from .d3net.encoding import D3netOperationMode

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -86,6 +87,11 @@ def native_value(self) -> float:
"""Setpoint temperature in the room."""
return self._unit.status.temp_setpoint

@property
def icon(self) -> str:
"""Icon for setpoint."""
return "mdi:thermometer-check"


class D3netSensorState(D3netSensorBase):
"""Sensor object for operating state data."""
Expand All @@ -104,3 +110,17 @@ def native_value(self) -> float:
if self._unit.status.power:
return ACTION_DAIKIN_HA[self._unit.status.operating_current].name
return HVACAction.OFF.name

@property
def icon(self) -> str:
"""Icon for setpoint."""
if not self._unit.status.power:
return "mdi:power-standby"
match self._unit.status.operating_current:
case D3netOperationMode.FAN:
return "mdi:fan"
case D3netOperationMode.HEAT:
return "mdi:fire"
case D3netOperationMode.COOL:
return "mdi:snowflake"
return "mdi:help"

0 comments on commit 8f9d0fd

Please sign in to comment.