hOn/custom_components/hon/binary_sensor.py
danyrd92 e01d19efd6
Add Preheat Binary Sensor for Ovens + Fix Attribute Handling for Binary Sensors (#17)
This PR introduces a new binary sensor for Haier ovens based on the preheatStatus attribute. While implementing this feature, I discovered that the integration does not correctly evaluate certain appliance attributes, which caused binary sensors to always report off even when the underlying value indicated they should be on.

Issue Identified

Attributes such as preheatStatus and onOffStatus are not returned as raw integers. Instead, the integration receives them as HonAttribute objects. Because the existing logic compares the object itself directly against the expected value, the expression value == on_value always evaluates to False.

Fix Implemented

To ensure proper evaluation, the binary sensor logic has been updated so that when an attribute is a HonAttribute instance, the sensor extracts its actual value using:

value = attr.value if hasattr(attr, "value") else attr


This fix has been applied to both:

is_on()

_handle_coordinator_update()

As a result, the newly added preheatStatus sensor works correctly, and the previously existing onOffStatus binary sensor now functions reliably as well.

Potential Impact on Other Sensors

This change may also correct the behavior of other binary sensors that rely on attributes following the same pattern. I cannot verify all of them because I only have access to a single Haier oven, but the patch is safe, backward-compatible, and should improve the accuracy of any affected entities.
2025-12-16 17:37:03 +01:00

357 lines
12 KiB
Python

import logging
from dataclasses import dataclass
from homeassistant.components.binary_sensor import (
BinarySensorEntityDescription,
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .entity import HonEntity
from .util import unique_entities
_LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True)
class HonBinarySensorEntityDescription(BinarySensorEntityDescription):
on_value: str | float = ""
BINARY_SENSORS: dict[str, tuple[HonBinarySensorEntityDescription, ...]] = {
"WM": (
HonBinarySensorEntityDescription(
key="attributes.lastConnEvent.category",
name="Remote Control",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED",
icon="mdi:remote",
translation_key="remote_control",
),
HonBinarySensorEntityDescription(
key="doorLockStatus",
name="Door Lock",
device_class=BinarySensorDeviceClass.LOCK,
on_value=0,
translation_key="door_lock",
),
HonBinarySensorEntityDescription(
key="doorStatus",
name="Door",
device_class=BinarySensorDeviceClass.DOOR,
on_value=1,
translation_key="door_open",
),
HonBinarySensorEntityDescription(
key="prewash",
icon="mdi:tshirt-crew",
name="Pre Wash",
translation_key="prewash",
),
HonBinarySensorEntityDescription(
key="extraRinse1",
icon="mdi:numeric-1-box-multiple-outline",
name="Extra Rinse 1",
translation_key="extra_rinse_1",
),
HonBinarySensorEntityDescription(
key="extraRinse2",
icon="mdi:numeric-2-box-multiple-outline",
name="Extra Rinse 2",
translation_key="extra_rinse_2",
),
HonBinarySensorEntityDescription(
key="extraRinse3",
icon="mdi:numeric-3-box-multiple-outline",
name="Extra Rinse 3",
translation_key="extra_rinse_3",
),
HonBinarySensorEntityDescription(
key="goodNight",
icon="mdi:weather-night",
name="Good Night Mode",
translation_key="good_night",
),
HonBinarySensorEntityDescription(
key="acquaplus",
icon="mdi:water-plus",
name="Acqua Plus",
translation_key="acqua_plus",
),
),
"TD": (
HonBinarySensorEntityDescription(
key="attributes.lastConnEvent.category",
name="Connection",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED",
translation_key="connection",
),
HonBinarySensorEntityDescription(
key="doorStatus",
name="Door",
device_class=BinarySensorDeviceClass.DOOR,
on_value=1,
translation_key="door_open",
),
HonBinarySensorEntityDescription(
key="anticrease",
name="Anti-Crease",
icon="mdi:iron",
translation_key="anti_crease",
),
),
"OV": (
HonBinarySensorEntityDescription(
key="attributes.lastConnEvent.category",
name="Connection",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED",
icon="mdi:wifi",
translation_key="connection",
),
HonBinarySensorEntityDescription(
key="attributes.parameters.onOffStatus",
name="On",
device_class=BinarySensorDeviceClass.RUNNING,
on_value=1,
icon="mdi:power-cycle",
translation_key="on",
),
HonBinarySensorEntityDescription(
key="attributes.parameters.preheatStatus",
name="Pre-Heat",
device_class=BinarySensorDeviceClass.RUNNING,
on_value=1,
icon="mdi:thermometer-chevron-up",
translation_key="pre_heat",
),
),
"IH": (
HonBinarySensorEntityDescription(
key="attributes.lastConnEvent.category",
name="Connection",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED",
icon="mdi:wifi",
translation_key="connection",
),
HonBinarySensorEntityDescription(
key="attributes.parameters.onOffStatus",
name="On",
device_class=BinarySensorDeviceClass.RUNNING,
on_value=1,
icon="mdi:power-cycle",
translation_key="on",
),
HonBinarySensorEntityDescription(
key="hotStatus",
name="Hot Status",
device_class=BinarySensorDeviceClass.HEAT,
on_value=1,
translation_key="still_hot",
),
HonBinarySensorEntityDescription(
key="panStatus",
name="Pan Status",
on_value=1,
icon="mdi:pot-mix",
translation_key="pan_status",
),
HonBinarySensorEntityDescription(
key="hobLockStatus",
name="Hob Lock",
device_class=BinarySensorDeviceClass.LOCK,
on_value=0,
translation_key="child_lock",
),
),
"DW": (
HonBinarySensorEntityDescription(
key="saltStatus",
name="Salt",
device_class=BinarySensorDeviceClass.PROBLEM,
on_value=1,
icon="mdi:shaker-outline",
translation_key="salt_level",
),
HonBinarySensorEntityDescription(
key="rinseAidStatus",
name="Rinse Aid",
device_class=BinarySensorDeviceClass.PROBLEM,
on_value=1,
icon="mdi:spray-bottle",
translation_key="rinse_aid",
),
HonBinarySensorEntityDescription(
key="attributes.lastConnEvent.category",
name="Connection",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED",
translation_key="connection",
),
HonBinarySensorEntityDescription(
key="doorStatus",
name="Door",
device_class=BinarySensorDeviceClass.DOOR,
on_value=1,
translation_key="door_open",
),
),
"AC": (
HonBinarySensorEntityDescription(
key="filterChangeStatusLocal",
name="Filter Replacement",
device_class=BinarySensorDeviceClass.PROBLEM,
on_value=1,
translation_key="filter_replacement",
),
HonBinarySensorEntityDescription(
key="ch2oCleaningStatus",
name="Ch2O Cleaning",
on_value=1,
),
),
"REF": (
HonBinarySensorEntityDescription(
key="quickModeZ1",
name="Super Cool",
icon="mdi:snowflake",
device_class=BinarySensorDeviceClass.RUNNING,
on_value=1,
translation_key="super_cool",
),
HonBinarySensorEntityDescription(
key="quickModeZ2",
name="Super Freeze",
icon="mdi:snowflake-variant",
device_class=BinarySensorDeviceClass.RUNNING,
on_value=1,
translation_key="super_freeze",
),
HonBinarySensorEntityDescription(
key="doorStatusZ1",
name="Door1 Status Fridge",
device_class=BinarySensorDeviceClass.DOOR,
icon="mdi:fridge-top",
on_value=1,
translation_key="fridge_door",
),
HonBinarySensorEntityDescription(
key="door2StatusZ1",
name="Door2 Status Fridge",
icon="mdi:fridge-top",
device_class=BinarySensorDeviceClass.DOOR,
on_value=1,
translation_key="fridge_door",
),
HonBinarySensorEntityDescription(
key="doorStatusZ2",
name="Door1 Status Freezer",
icon="mdi:fridge-bottom",
device_class=BinarySensorDeviceClass.DOOR,
on_value=1,
translation_key="freezer_door",
),
HonBinarySensorEntityDescription(
key="door2StatusZ2",
name="Door2 Status Freezer",
icon="mdi:fridge-bottom",
device_class=BinarySensorDeviceClass.DOOR,
on_value=1,
translation_key="freezer_door",
),
HonBinarySensorEntityDescription(
key="intelligenceMode",
name="Auto-Set Mode",
icon="mdi:thermometer-auto",
device_class=BinarySensorDeviceClass.RUNNING,
on_value=1,
translation_key="auto_set",
),
HonBinarySensorEntityDescription(
key="holidayMode",
name="Holiday Mode",
icon="mdi:palm-tree",
device_class=BinarySensorDeviceClass.RUNNING,
on_value=1,
translation_key="holiday_mode",
),
),
"AP": (
HonBinarySensorEntityDescription(
key="attributes.parameters.onOffStatus",
name="On",
device_class=BinarySensorDeviceClass.RUNNING,
on_value="1",
icon="mdi:power-cycle",
translation_key="on",
),
),
"FRE": (
HonBinarySensorEntityDescription(
key="quickModeZ1",
name="Super Cool",
icon="mdi:snowflake",
device_class=BinarySensorDeviceClass.RUNNING,
on_value=1,
translation_key="super_cool",
),
HonBinarySensorEntityDescription(
key="quickModeZ2",
name="Super Freeze",
icon="mdi:snowflake-variant",
device_class=BinarySensorDeviceClass.RUNNING,
on_value=1,
translation_key="super_freeze",
),
HonBinarySensorEntityDescription(
key="doorStatusZ2",
name="Door Status",
icon="mdi:fridge",
device_class=BinarySensorDeviceClass.DOOR,
on_value=1,
translation_key="door_open",
),
),
}
BINARY_SENSORS["WD"] = unique_entities(BINARY_SENSORS["WM"], BINARY_SENSORS["TD"])
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
entities = []
for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in BINARY_SENSORS.get(device.appliance_type, []):
if device.get(description.key) is None:
continue
entity = HonBinarySensorEntity(hass, entry, device, description)
entities.append(entity)
async_add_entities(entities)
class HonBinarySensorEntity(HonEntity, BinarySensorEntity):
entity_description: HonBinarySensorEntityDescription
@property
def is_on(self) -> bool:
attr = self._device.get(self.entity_description.key, None)
value = attr.value if hasattr(attr, "value") else attr
return value == self.entity_description.on_value
@callback
def _handle_coordinator_update(self, update: bool = True) -> None:
attr = self._device.get(self.entity_description.key, None)
value = attr.value if hasattr(attr, "value") else attr
self._attr_native_value = (value == self.entity_description.on_value)
if update:
self.schedule_update_ha_state()