Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions tests/test_xiaomi.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@
ZCL_PORTIONS_DISPENSED,
ZCL_SERVING_SIZE,
ZCL_WEIGHT_DISPENSED,
AqaraFeederAcn001,
FeedingMode,
FeedingSource,
OppleCluster,
)
from zhaquirks.xiaomi.aqara.light_acn import AqaraLightT1M, LumiPowerOnStateMode
import zhaquirks.xiaomi.aqara.magnet_ac01
Expand Down Expand Up @@ -1074,11 +1074,20 @@ async def test_xiaomi_total_active_power_clear(zigpy_device_from_quirk):
],
)
async def test_aqara_feeder_write_attrs(
zigpy_device_from_quirk, attribute, value, expected_bytes
zigpy_device_from_v2_quirk, attribute, value, expected_bytes
):
"""Test Aqara C1 pet feeder attr writing."""

device = zigpy_device_from_quirk(AqaraFeederAcn001)
device = zigpy_device_from_v2_quirk(
"Aqara",
"aqara.feeder.acn001",
cluster_ids={
Comment thread
Copilot marked this conversation as resolved.
1: {
OnOff.cluster_id: ClusterType.Server,
OppleCluster.cluster_id: ClusterType.Server,
}
},
)
opple_cluster = device.endpoints[1].opple_cluster
opple_cluster._write_attributes = mock.AsyncMock(
return_value=[
Expand Down Expand Up @@ -1117,7 +1126,9 @@ async def test_aqara_feeder_write_attrs(
3,
[
mock.call(ZCL_LAST_FEEDING_SIZE, 3, mock.ANY),
mock.call(ZCL_LAST_FEEDING_SOURCE, FeedingSource.Remote, mock.ANY),
mock.call(
ZCL_LAST_FEEDING_SOURCE, FeedingSource.HomeAssistant, mock.ANY
),
mock.call(
FEEDER_ATTR, b"\x00\x05\xd0\x04\x15\x02\xbc\x040203", mock.ANY
),
Expand Down Expand Up @@ -1210,10 +1221,19 @@ async def test_aqara_feeder_write_attrs(
],
)
async def test_aqara_feeder_attr_reports(
zigpy_device_from_quirk, bytes_received, call_count, calls
zigpy_device_from_v2_quirk, bytes_received, call_count, calls
):
"""Test Aqara C1 pet feeder attr reports and parsing."""
device = zigpy_device_from_quirk(AqaraFeederAcn001)
device = zigpy_device_from_v2_quirk(
"Aqara",
"aqara.feeder.acn001",
cluster_ids={
1: {
OnOff.cluster_id: ClusterType.Server,
OppleCluster.cluster_id: ClusterType.Server,
}
},
)
opple_cluster = device.endpoints[1].opple_cluster

attribute_updates: list[tuple[int, Any]] = []
Expand Down
199 changes: 115 additions & 84 deletions zhaquirks/xiaomi/aqara/feeder_acn001.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,24 @@
"""Quirk for Aqara aqara.feeder.acn001."""
"""Quirk V2 for Aqara aqara.feeder.acn001."""

from __future__ import annotations

import logging
from typing import Any, Final

from zigpy import types
from zigpy.profiles import zgp, zha
from zigpy.zcl import AttributeReportedEvent, AttributeUpdatedEvent, foundation
from zigpy.zcl.clusters.general import (
Basic,
GreenPowerProxy,
Groups,
Identify,
OnOff,
Ota,
Scenes,
Time,
)
from zigpy.zcl.clusters.general import OnOff, Time
from zigpy.zcl.foundation import BaseAttributeDefs, ZCLAttributeDef

from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MANUFACTURER,
MODEL,
OUTPUT_CLUSTERS,
PROFILE_ID,
from zhaquirks.builder import (
BinarySensorDeviceClass,
EntityPlatform,
EntityType,
QuirkBuilder,
SensorStateClass,
UnitOfMass,
)
from zhaquirks.xiaomi import XiaomiAqaraE1Cluster, XiaomiCustomDevice
from zhaquirks.xiaomi import XiaomiAqaraE1Cluster

# 32 bit signed integer values that are encoded in FEEDER_ATTR = 0xFFF1
FEEDING = 0x04150055
Expand Down Expand Up @@ -86,8 +75,9 @@
class FeedingSource(types.enum8):
"""Feeding source."""

Schedule = 0x00
Feeder = 0x01
Remote = 0x02
HomeAssistant = 0x02


class FeedingMode(types.enum8):
Expand Down Expand Up @@ -274,65 +264,106 @@ async def write_attributes(
return await super().write_attributes(attrs, update_cache=False, **kwargs)


class AqaraFeederAcn001(XiaomiCustomDevice):
"""Aqara aqara.feeder.acn001 custom device implementation."""

signature = {
MODEL: "aqara.feeder.acn001",
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_OUTPUT,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
OnOff.cluster_id,
OppleCluster.cluster_id,
],
OUTPUT_CLUSTERS: [
Identify.cluster_id,
Ota.cluster_id,
],
},
242: {
PROFILE_ID: zgp.PROFILE_ID,
DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [
GreenPowerProxy.cluster_id,
],
},
},
}

replacement = {
MANUFACTURER: "Aqara",
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.ON_OFF_OUTPUT,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
OppleCluster,
Time.cluster_id,
],
OUTPUT_CLUSTERS: [
Identify.cluster_id,
Ota.cluster_id,
],
},
242: {
PROFILE_ID: zgp.PROFILE_ID,
DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [
GreenPowerProxy.cluster_id,
],
},
},
}
(
QuirkBuilder(None, "aqara.feeder.acn001")
Comment thread
Copilot marked this conversation as resolved.
.friendly_name(manufacturer="Aqara", model="aqara.feeder.acn001")
.removes(OnOff.cluster_id)
.replaces(Time)
.replaces(OppleCluster)
.enum(
attribute_name=OppleCluster.AttributeDefs.last_feeding_source.name,
enum_class=FeedingSource,
cluster_id=OppleCluster.cluster_id,
entity_platform=EntityPlatform.SENSOR,
entity_type=EntityType.STANDARD,
unique_id_suffix="64704-last_feeding_source",
translation_key="last_feeding_source",
fallback_name="Last feeding source",
)
.sensor(
attribute_name=OppleCluster.AttributeDefs.last_feeding_size.name,
cluster_id=OppleCluster.cluster_id,
unique_id_suffix="64704-last_feeding_size",
translation_key="last_feeding_size",
fallback_name="Last feeding size",
)
.sensor(
attribute_name=OppleCluster.AttributeDefs.portions_dispensed.name,
cluster_id=OppleCluster.cluster_id,
state_class=SensorStateClass.TOTAL_INCREASING,
Comment thread
iamjoshk marked this conversation as resolved.
unique_id_suffix="64704-portions_dispensed",
translation_key="portions_dispensed_today",
fallback_name="Portions dispensed today",
)
.sensor(
attribute_name=OppleCluster.AttributeDefs.weight_dispensed.name,
cluster_id=OppleCluster.cluster_id,
unit=UnitOfMass.GRAMS,
state_class=SensorStateClass.TOTAL_INCREASING,
unique_id_suffix="64704-weight_dispensed",
translation_key="weight_dispensed_today",
fallback_name="Weight dispensed today",
)
.switch(
attribute_name=OppleCluster.AttributeDefs.disable_led_indicator.name,
cluster_id=OppleCluster.cluster_id,
force_inverted=True,
unique_id_suffix="64704-disable_led_indicator",
translation_key="led_indicator",
fallback_name="LED indicator",
)
.switch(
attribute_name=OppleCluster.AttributeDefs.child_lock.name,
cluster_id=OppleCluster.cluster_id,
unique_id_suffix="64704-child_lock",
translation_key="child_lock",
fallback_name="Child lock",
)
.enum(
attribute_name=OppleCluster.AttributeDefs.feeding_mode.name,
enum_class=FeedingMode,
cluster_id=OppleCluster.cluster_id,
unique_id_suffix="64704-feeding_mode",
translation_key="feeding_mode",
fallback_name="Feeding mode",
)
.number(
attribute_name=OppleCluster.AttributeDefs.serving_size.name,
cluster_id=OppleCluster.cluster_id,
min_value=1,
max_value=10,
mode="box",
unique_id_suffix="64704-serving_size",
translation_key="serving_size",
fallback_name="Serving size",
)
.number(
attribute_name=OppleCluster.AttributeDefs.portion_weight.name,
cluster_id=OppleCluster.cluster_id,
min_value=1,
max_value=100,
unit=UnitOfMass.GRAMS,
mode="box",
unique_id_suffix="64704-portion_weight",
translation_key="portion_weight",
fallback_name="Portion weight",
)
.binary_sensor(
attribute_name=OppleCluster.AttributeDefs.error_detected.name,
cluster_id=OppleCluster.cluster_id,
entity_type=EntityType.STANDARD,
device_class=BinarySensorDeviceClass.PROBLEM,
unique_id_suffix="64704-error_detected",
fallback_name="Error detected",
)
.write_attr_button(
attribute_name=OppleCluster.AttributeDefs.feeding.name,
attribute_value=1,
cluster_id=OppleCluster.cluster_id,
entity_type=EntityType.STANDARD,
unique_id_suffix="64704-feeding",
translation_key="feed",
fallback_name="Feed",
)
.add_to_registry()
)
Loading