mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 05:37:44 +00:00
Add Aqara c1 pet feeder support to ZHA (#82340)
* Add Aqara c1 pet feeder support to ZHA * clean up * cleanup * state classes for daily measurements * cleanups * cleanups * restore the refreshing of the inverted value cache * cleanup
This commit is contained in:
parent
d47fe35a88
commit
5329a679bb
@ -194,3 +194,12 @@ class ReplaceFilter(BinarySensor, id_suffix="replace_filter"):
|
||||
|
||||
SENSOR_ATTR = "replace_filter"
|
||||
_attr_device_class: BinarySensorDeviceClass = BinarySensorDeviceClass.PROBLEM
|
||||
|
||||
|
||||
@MULTI_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederErrorDetected(BinarySensor, id_suffix="error_detected"):
|
||||
"""ZHA aqara pet feeder error detected binary sensor."""
|
||||
|
||||
SENSOR_ATTR = "error_detected"
|
||||
_attr_device_class: BinarySensorDeviceClass = BinarySensorDeviceClass.PROBLEM
|
||||
_attr_name: str = "Error detected"
|
||||
|
@ -177,3 +177,12 @@ class NoPresenceStatusResetButton(
|
||||
_attribute_value = 1
|
||||
_attr_device_class = ButtonDeviceClass.RESTART
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
|
||||
@MULTI_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederFeedButton(ZHAAttributeButton, id_suffix="feeding"):
|
||||
"""Defines a feed button for the aqara c1 pet feeder."""
|
||||
|
||||
_attribute_name = "feeding"
|
||||
_attr_name = "Feed"
|
||||
_attribute_value = 1
|
||||
|
@ -126,6 +126,17 @@ class OppleRemote(ZigbeeChannel):
|
||||
self.ZCL_INIT_ATTRS = {
|
||||
"power_outage_memory": True,
|
||||
}
|
||||
elif self.cluster.endpoint.model == "aqara.feeder.acn001":
|
||||
self.ZCL_INIT_ATTRS = {
|
||||
"portions_dispensed": True,
|
||||
"weight_dispensed": True,
|
||||
"error_detected": True,
|
||||
"disable_led_indicator": True,
|
||||
"child_lock": True,
|
||||
"feeding_mode": True,
|
||||
"serving_size": True,
|
||||
"portion_weight": True,
|
||||
}
|
||||
|
||||
async def async_initialize_channel_specific(self, from_cache: bool) -> None:
|
||||
"""Initialize channel specific."""
|
||||
|
@ -8,9 +8,9 @@ from typing import TYPE_CHECKING, Any, TypeVar
|
||||
import zigpy.exceptions
|
||||
from zigpy.zcl.foundation import Status
|
||||
|
||||
from homeassistant.components.number import NumberEntity
|
||||
from homeassistant.components.number import NumberEntity, NumberMode
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.const import Platform, UnitOfMass
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
@ -836,3 +836,32 @@ class InovelliDefaultAllLEDOffIntensity(
|
||||
_attr_native_max_value: float = 100
|
||||
_zcl_attribute: str = "led_intensity_when_off"
|
||||
_attr_name: str = "Default all LED off intensity"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederServingSize(ZHANumberConfigurationEntity, id_suffix="serving_size"):
|
||||
"""Aqara pet feeder serving size configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_native_min_value: float = 1
|
||||
_attr_native_max_value: float = 10
|
||||
_zcl_attribute: str = "serving_size"
|
||||
_attr_name: str = "Serving to dispense"
|
||||
_attr_mode: NumberMode = NumberMode.BOX
|
||||
_attr_icon: str = "mdi:counter"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederPortionWeight(
|
||||
ZHANumberConfigurationEntity, id_suffix="portion_weight"
|
||||
):
|
||||
"""Aqara pet feeder portion weight configuration entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_native_min_value: float = 1
|
||||
_attr_native_max_value: float = 100
|
||||
_zcl_attribute: str = "portion_weight"
|
||||
_attr_name: str = "Portion weight"
|
||||
_attr_mode: NumberMode = NumberMode.BOX
|
||||
_attr_native_unit_of_measurement: str = UnitOfMass.GRAMS
|
||||
_attr_icon: str = "mdi:weight-gram"
|
||||
|
@ -477,3 +477,20 @@ class InovelliSwitchTypeEntity(ZCLEnumSelectEntity, id_suffix="switch_type"):
|
||||
_select_attr = "switch_type"
|
||||
_enum = InovelliSwitchType
|
||||
_attr_name: str = "Switch type"
|
||||
|
||||
|
||||
class AqaraFeedingMode(types.enum8):
|
||||
"""Feeding mode."""
|
||||
|
||||
Manual = 0x00
|
||||
Schedule = 0x01
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederMode(ZCLEnumSelectEntity, id_suffix="feeding_mode"):
|
||||
"""Representation of an Aqara pet feeder mode configuration entity."""
|
||||
|
||||
_select_attr = "feeding_mode"
|
||||
_enum = AqaraFeedingMode
|
||||
_attr_name = "Mode"
|
||||
_attr_icon: str = "mdi:wrench-clock"
|
||||
|
@ -5,6 +5,8 @@ import functools
|
||||
import numbers
|
||||
from typing import TYPE_CHECKING, Any, TypeVar
|
||||
|
||||
from zigpy import types
|
||||
|
||||
from homeassistant.components.climate import HVACAction
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@ -36,6 +38,7 @@ from homeassistant.const import (
|
||||
VOLUME_GALLONS,
|
||||
VOLUME_LITERS,
|
||||
Platform,
|
||||
UnitOfMass,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
@ -837,3 +840,53 @@ class IkeaFilterRunTime(Sensor, id_suffix="filter_run_time"):
|
||||
_attr_icon = "mdi:timer"
|
||||
_attr_name: str = "Filter run time"
|
||||
_unit = TIME_MINUTES
|
||||
|
||||
|
||||
class AqaraFeedingSource(types.enum8):
|
||||
"""Aqara pet feeder feeding source."""
|
||||
|
||||
Feeder = 0x01
|
||||
HomeAssistant = 0x02
|
||||
|
||||
|
||||
@MULTI_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederLastFeedingSource(Sensor, id_suffix="last_feeding_source"):
|
||||
"""Sensor that displays the last feeding source of pet feeder."""
|
||||
|
||||
SENSOR_ATTR = "last_feeding_source"
|
||||
_attr_name: str = "Last feeding source"
|
||||
_attr_icon = "mdi:devices"
|
||||
|
||||
def formatter(self, value: int) -> int | float | None:
|
||||
"""Numeric pass-through formatter."""
|
||||
return AqaraFeedingSource(value).name
|
||||
|
||||
|
||||
@MULTI_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederLastFeedingSize(Sensor, id_suffix="last_feeding_size"):
|
||||
"""Sensor that displays the last feeding size of the pet feeder."""
|
||||
|
||||
SENSOR_ATTR = "last_feeding_size"
|
||||
_attr_name: str = "Last feeding size"
|
||||
_attr_icon: str = "mdi:counter"
|
||||
|
||||
|
||||
@MULTI_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederPortionsDispensed(Sensor, id_suffix="portions_dispensed"):
|
||||
"""Sensor that displays the number of portions dispensed by the pet feeder."""
|
||||
|
||||
SENSOR_ATTR = "portions_dispensed"
|
||||
_attr_name: str = "Portions dispensed today"
|
||||
_attr_state_class: SensorStateClass = SensorStateClass.TOTAL_INCREASING
|
||||
_attr_icon: str = "mdi:counter"
|
||||
|
||||
|
||||
@MULTI_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederWeightDispensed(Sensor, id_suffix="weight_dispensed"):
|
||||
"""Sensor that displays the weight weight dispensed by the pet feeder."""
|
||||
|
||||
SENSOR_ATTR = "weight_dispensed"
|
||||
_attr_name: str = "Weight dispensed today"
|
||||
_unit = UnitOfMass.GRAMS
|
||||
_attr_state_class: SensorStateClass = SensorStateClass.TOTAL_INCREASING
|
||||
_attr_icon: str = "mdi:weight-gram"
|
||||
|
@ -174,7 +174,8 @@ class ZHASwitchConfigurationEntity(ZhaEntity, SwitchEntity):
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_zcl_attribute: str
|
||||
_zcl_inverter_attribute: str = ""
|
||||
_zcl_inverter_attribute: str | None = None
|
||||
_force_inverted: bool = False
|
||||
|
||||
@classmethod
|
||||
def create_entity(
|
||||
@ -225,19 +226,24 @@ class ZHASwitchConfigurationEntity(ZhaEntity, SwitchEntity):
|
||||
"""Handle state update from channel."""
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def inverted(self) -> bool:
|
||||
"""Return True if the switch is inverted."""
|
||||
if self._zcl_inverter_attribute:
|
||||
return bool(self._channel.cluster.get(self._zcl_inverter_attribute))
|
||||
return self._force_inverted
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return if the switch is on based on the statemachine."""
|
||||
val = bool(self._channel.cluster.get(self._zcl_attribute))
|
||||
invert = bool(self._channel.cluster.get(self._zcl_inverter_attribute))
|
||||
return (not val) if invert else val
|
||||
return (not val) if self.inverted else val
|
||||
|
||||
async def async_turn_on_off(self, state: bool) -> None:
|
||||
"""Turn the entity on or off."""
|
||||
try:
|
||||
invert = bool(self._channel.cluster.get(self._zcl_inverter_attribute))
|
||||
result = await self._channel.cluster.write_attributes(
|
||||
{self._zcl_attribute: not state if invert else state}
|
||||
{self._zcl_attribute: not state if self.inverted else state}
|
||||
)
|
||||
except zigpy.exceptions.ZigbeeException as ex:
|
||||
self.error("Could not set value: %s", ex)
|
||||
@ -258,15 +264,15 @@ class ZHASwitchConfigurationEntity(ZhaEntity, SwitchEntity):
|
||||
async def async_update(self) -> None:
|
||||
"""Attempt to retrieve the state of the entity."""
|
||||
await super().async_update()
|
||||
_LOGGER.error("Polling current state")
|
||||
self.error("Polling current state")
|
||||
if self._channel:
|
||||
value = await self._channel.get_attribute_value(
|
||||
self._zcl_attribute, from_cache=False
|
||||
)
|
||||
invert = await self._channel.get_attribute_value(
|
||||
await self._channel.get_attribute_value(
|
||||
self._zcl_inverter_attribute, from_cache=False
|
||||
)
|
||||
_LOGGER.debug("read value=%s, inverter=%s", value, bool(invert))
|
||||
self.debug("read value=%s, inverted=%s", value, self.inverted)
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(
|
||||
@ -430,3 +436,24 @@ class InovelliDisableDoubleTapClearNotificationsMode(
|
||||
|
||||
_zcl_attribute: str = "disable_clear_notifications_double_tap"
|
||||
_attr_name: str = "Disable config 2x tap to clear notifications"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederLEDIndicator(
|
||||
ZHASwitchConfigurationEntity, id_suffix="disable_led_indicator"
|
||||
):
|
||||
"""Representation of a LED indicator configuration entity."""
|
||||
|
||||
_zcl_attribute: str = "disable_led_indicator"
|
||||
_attr_name = "LED indicator"
|
||||
_force_inverted = True
|
||||
_attr_icon: str = "mdi:led-on"
|
||||
|
||||
|
||||
@CONFIG_DIAGNOSTIC_MATCH(channel_names="opple_cluster", models={"aqara.feeder.acn001"})
|
||||
class AqaraPetFeederChildLock(ZHASwitchConfigurationEntity, id_suffix="child_lock"):
|
||||
"""Representation of a child lock configuration entity."""
|
||||
|
||||
_zcl_attribute: str = "child_lock"
|
||||
_attr_name = "Child lock"
|
||||
_attr_icon: str = "mdi:account-lock"
|
||||
|
Loading…
x
Reference in New Issue
Block a user