Skip to content

Commit

Permalink
introduce binary sensor for humidification and water error
Browse files Browse the repository at this point in the history
  • Loading branch information
kongo09 committed Mar 17, 2024
1 parent 5700d08 commit a19b1c0
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 2 deletions.
4 changes: 2 additions & 2 deletions custom_components/philips_airpurifier_coap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
_LOGGER = logging.getLogger(__name__)


PLATFORMS = ["fan", "sensor", "switch", "light", "select", "number"]
PLATFORMS = ["fan", "binary_sensor", "sensor", "switch", "light", "select", "number"]


# icons code thanks to Thomas Loven:
Expand Down Expand Up @@ -117,7 +117,7 @@ async def async_get_mac_address_from_host(hass: HomeAssistant, host: str) -> str
)
if not mac_address:
return None

return format_mac(mac_address)


Expand Down
114 changes: 114 additions & 0 deletions custom_components/philips_airpurifier_coap/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Philips Air Purifier & Humidifier Binary Sensors."""
from __future__ import annotations

from collections.abc import Callable
import logging
from typing import Any, cast

from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_HOST,
CONF_NAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import Entity

from .const import (
BINARY_SENSOR_TYPES,
CONF_MODEL,
DATA_KEY_COORDINATOR,
DOMAIN,
FanAttributes,
PhilipsApi,
)
from .philips import Coordinator, PhilipsEntity, model_to_class

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry( # noqa: D103
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[list[Entity], bool], None],
) -> None:
_LOGGER.debug("async_setup_entry called for platform binary_sensor")

host = entry.data[CONF_HOST]
model = entry.data[CONF_MODEL]
name = entry.data[CONF_NAME]

data = hass.data[DOMAIN][host]

coordinator = data[DATA_KEY_COORDINATOR]
status = coordinator.status

model_class = model_to_class.get(model)
available_binary_sensors = []

if model_class:
for cls in reversed(model_class.__mro__):
cls_available_binary_sensors = getattr(cls, "AVAILABLE_BINARY_SENSORS", [])
available_binary_sensors.extend(cls_available_binary_sensors)

binary_sensors = []

for binary_sensor in BINARY_SENSOR_TYPES:
if binary_sensor in status and binary_sensor in available_binary_sensors:
binary_sensors.append(
PhilipsBinarySensor(coordinator, name, model, binary_sensor)
)

async_add_entities(binary_sensors, update_before_add=False)


class PhilipsBinarySensor(PhilipsEntity, BinarySensorEntity):
"""Define a Philips AirPurifier binary_sensor."""

def __init__( # noqa: D107
self, coordinator: Coordinator, name: str, model: str, kind: str
) -> None:
super().__init__(coordinator)
self._model = model
self._description = BINARY_SENSOR_TYPES[kind]
self._icon_map = self._description.get(FanAttributes.ICON_MAP)
self._norm_icon = (
next(iter(self._icon_map.items()))[1]
if self._icon_map is not None
else None
)
self._attr_device_class = self._description.get(ATTR_DEVICE_CLASS)
self._attr_entity_category = self._description.get(CONF_ENTITY_CATEGORY)
self._attr_name = (
f"{name} {self._description[FanAttributes.LABEL].replace('_', ' ').title()}"
)

try:
device_id = self._device_status[PhilipsApi.DEVICE_ID]
self._attr_unique_id = f"{self._model}-{device_id}-{kind.lower()}"
except Exception as e:
_LOGGER.error("Failed retrieving unique_id: %s", e)
raise PlatformNotReady
self._attrs: dict[str, Any] = {}
self.kind = kind

@property
def is_on(self) -> bool:
"""Return the state of the binary sensor."""
value = self._device_status[self.kind]
convert = self._description.get(FanAttributes.VALUE)
if convert:
value = convert(value)
return cast(bool, value)

@property
def icon(self) -> str:
"""Return the icon of the binary sensor."""
icon = self._norm_icon
if not self._icon_map:
return icon

return self._icon_map[self.is_on]
46 changes: 46 additions & 0 deletions custom_components/philips_airpurifier_coap/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ class FanAttributes(StrEnum):
FILTER_NANOPROTECT_CLEAN = "pre_filter"
FUNCTION = "function"
HUMIDITY = "humidity"
HUMIDIFIER = "humidification"
HUMIDITY_TARGET = "humidity_target"
INDOOR_ALLERGEN_INDEX = "indoor_allergen_index"
LABEL = "label"
Expand Down Expand Up @@ -262,6 +263,7 @@ class FanAttributes(StrEnum):
TARGET_TEMP = "target_temperature"
STANDBY_SENSORS = "standby_sensors"
AUTO_PLUS = "auto_plus"
WATER_TANK = "water_tank"


class FanUnits(StrEnum):
Expand Down Expand Up @@ -596,6 +598,50 @@ class PhilipsApi:
# },
}

BINARY_SENSOR_TYPES: dict[str, SensorDescription] = {
# binary device sensors
PhilipsApi.ERROR_CODE: {
# test for out of water error, which is in bit 9 of the error number
FanAttributes.ICON_MAP: {
True: "mdi:water",
False: "mdi:water-off",
},
FanAttributes.LABEL: FanAttributes.WATER_TANK,
ATTR_DEVICE_CLASS: SensorDeviceClass.MOISTURE,
FanAttributes.VALUE: lambda value: not value & (1 << 8),
CONF_ENTITY_CATEGORY: EntityCategory.DIAGNOSTIC,
},
PhilipsApi.NEW2_ERROR_CODE: {
# test for out of water error, which is in bit 9 of the error number
FanAttributes.ICON_MAP: {
True: "mdi:water",
False: "mdi:water-off",
},
FanAttributes.LABEL: FanAttributes.WATER_TANK,
ATTR_DEVICE_CLASS: SensorDeviceClass.MOISTURE,
FanAttributes.VALUE: lambda value: not value & (1 << 8),
CONF_ENTITY_CATEGORY: EntityCategory.DIAGNOSTIC,
},
PhilipsApi.FUNCTION: {
# test if the water container is available and thus humidification switched on
FanAttributes.ICON_MAP: {
True: PhilipsApi.FUNCTION_MAP["PH"][1],
False: PhilipsApi.FUNCTION_MAP["P"][1],
},
FanAttributes.LABEL: FanAttributes.HUMIDIFIER,
FanAttributes.VALUE: lambda value: value == "PH",
},
PhilipsApi.NEW2_MODE_A: {
# test if the water container is available and thus humidification switched on
FanAttributes.ICON_MAP: {
True: PhilipsApi.FUNCTION_MAP["PH"][1],
False: PhilipsApi.FUNCTION_MAP["P"][1],
},
FanAttributes.LABEL: FanAttributes.HUMIDIFIER,
FanAttributes.VALUE: lambda value: value == 4,
},
}

FILTER_TYPES: dict[str, FilterDescription] = {
PhilipsApi.FILTER_PRE: {
FanAttributes.ICON_MAP: {0: ICON.FILTER_REPLACEMENT, 72: "mdi:dots-grid"},
Expand Down
3 changes: 3 additions & 0 deletions custom_components/philips_airpurifier_coap/philips.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ class PhilipsGenericCoAPFanBase(PhilipsGenericFan):
AVAILABLE_SWITCHES = []
AVAILABLE_LIGHTS = []
AVAILABLE_NUMBERS = []
AVAILABLE_BINARY_SENSORS = []

KEY_PHILIPS_POWER = PhilipsApi.POWER
STATE_POWER_ON = "1"
Expand Down Expand Up @@ -635,6 +636,7 @@ class PhilipsHumidifierMixin(PhilipsGenericCoAPFanBase):
"""Mixin for humidifiers."""

AVAILABLE_SELECTS = [PhilipsApi.FUNCTION, PhilipsApi.HUMIDITY_TARGET]
AVAILABLE_BINARY_SENSORS = [PhilipsApi.ERROR_CODE]


# similar to the AC1715, the AC0850 seems to be a new class of devices that
Expand Down Expand Up @@ -1225,6 +1227,7 @@ class PhilipsAC3737(PhilipsNew2GenericCoAPFan):
AVAILABLE_LIGHTS = [PhilipsApi.NEW2_DISPLAY_BACKLIGHT2]
AVAILABLE_SWITCHES = [PhilipsApi.NEW2_CHILD_LOCK]
UNAVAILABLE_SENSORS = [PhilipsApi.NEW2_FAN_SPEED]
AVAILABLE_BINARY_SENSORS = [PhilipsApi.NEW2_ERROR_CODE, PhilipsApi.NEW2_MODE_A]


class PhilipsAC3829(PhilipsHumidifierMixin, PhilipsGenericCoAPFan):
Expand Down

0 comments on commit a19b1c0

Please sign in to comment.