Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Aqara Wall Outlet H2 EU #3653

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
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
15 changes: 10 additions & 5 deletions zhaquirks/xiaomi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,20 @@ def _parse_aqara_attributes(self, value):
{
100: TEMPERATURE_MEASUREMENT,
101: HUMIDITY_MEASUREMENT,
102: TVOC_MEASUREMENT
if self.endpoint.device.model == "lumi.airmonitor.acn01"
else PRESSURE_MEASUREMENT_PRECISION
if self.endpoint.device.model == "lumi.weather"
else PRESSURE_MEASUREMENT,
102: (
TVOC_MEASUREMENT
if self.endpoint.device.model == "lumi.airmonitor.acn01"
else (
PRESSURE_MEASUREMENT_PRECISION
if self.endpoint.device.model == "lumi.weather"
else PRESSURE_MEASUREMENT
)
),
}
)
elif self.endpoint.device.model in [
"lumi.plug",
"lumi.plug.aeu001",
"lumi.plug.maus01",
"lumi.plug.maeu01",
"lumi.plug.mmeu01",
Expand Down
107 changes: 107 additions & 0 deletions zhaquirks/xiaomi/aqara/plug_eu.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
"""Xiaomi Aqara EU plugs."""

from enum import Enum
import logging

import zigpy
from zigpy import types
from zigpy.profiles import zgp, zha
from zigpy.quirks.v2 import QuirkBuilder
from zigpy.quirks.v2.homeassistant import UnitOfPower
from zigpy.zcl.clusters.general import (
Alarms,
AnalogInput,
Expand All @@ -17,6 +22,7 @@
Time,
)
from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
from zigpy.zcl.clusters.measurement import TemperatureMeasurement
from zigpy.zcl.clusters.smartenergy import Metering

from zhaquirks.const import (
Expand All @@ -39,6 +45,8 @@

OPPLE_MFG_CODE = 0x115F

_LOGGER = logging.getLogger(__name__)


async def remove_from_ep(dev: zigpy.device.Device) -> None:
"""Remove devices that are in group 0 by default, so IKEA devices don't control them.
Expand Down Expand Up @@ -346,3 +354,102 @@
}

replacement = PlugMAEU01.replacement


class AqaraPowerOutageMemoryEnum(types.uint8_t, Enum):
"""Power Outage Memory enum."""

On = 0x00
Previous = 0x01
Off = 0x02
Inverted = 0x03


class PlugAEU001Cluster(XiaomiAqaraE1Cluster):
"""Custom cluster for Aqara lumi plug AEU001."""

attributes = {
0x0200: ("button_lock", types.uint8_t, True),
0x0202: ("charging_protection", types.Bool, True),
0x0203: ("led_indicator", types.Bool, True),
0x0206: ("charging_limit", types.Single, True),
0x020B: ("overload_protection", types.Single, True),
0x0517: ("power_on_behavior", AqaraPowerOutageMemoryEnum, True),
}


class PlugAEU001MeteringCluster(MeteringCluster):
"""Custom cluster for Aqara lumi plug AEU001."""

def _update_attribute(self, attrid, value):
if attrid == self.CURRENT_SUMM_DELIVERED_ID:
current_value = self._attr_cache.get(attrid, 0)
if value < current_value:
_LOGGER.debug(

Check warning on line 388 in zhaquirks/xiaomi/aqara/plug_eu.py

View check run for this annotation

Codecov / codecov/patch

zhaquirks/xiaomi/aqara/plug_eu.py#L385-L388

Added lines #L385 - L388 were not covered by tests
"Ignoring attribute update for %s: new value %s is less than current value %s",
attrid,
value,
current_value,
)
return
super()._update_attribute(attrid, value)

Check warning on line 395 in zhaquirks/xiaomi/aqara/plug_eu.py

View check run for this annotation

Codecov / codecov/patch

zhaquirks/xiaomi/aqara/plug_eu.py#L394-L395

Added lines #L394 - L395 were not covered by tests
Comment on lines +381 to +395
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'll have to check back on this. If the custom Xiaomi reports are more up-to-date/accurate and are also always sent, we could just ignore the ZCL reports coming in by overriding handle_cluster_general_request like this:

class LocalOccupancyCluster(LocalDataCluster, OccupancyCluster):
"""Local occupancy cluster that ignores messages from device."""
def handle_cluster_general_request(
self,
hdr: zigpy.zcl.foundation.ZCLHeader,
args: list,
*,
dst_addressing: AddressingMode | None = None,
) -> None:
"""Ignore occupancy attribute reports on this cluster, as they're invalid and sent by the sensor every hour."""

That would ignore all attribute reports from the EM cluster though, since we essentially remove all of this: https://github.com/zigpy/zigpy/blob/07e0451ce5317185e04b38e43aeb9fb168c62f28/zigpy/zcl/__init__.py#L464-L530

We could probably also block the calls to set up attribute reporting for that attribute, but we should still block incoming attribute reports, as some devices may have been connected before.

I'll need to come back to this. Hopefully soon.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if/how I could help in this regard.



(
QuirkBuilder("Aqara", "lumi.plug.aeu001")
.friendly_name(model="Wall Outlet H2 EU", manufacturer="Aqara")
.removes(TemperatureMeasurement.cluster_id)
.adds(DeviceTemperature)
.removes(OnOff.cluster_id, endpoint_id=2)
.replaces(BasicCluster)
.replaces(PlugAEU001MeteringCluster)
.replaces(ElectricalMeasurementCluster)
.replaces(PlugAEU001Cluster)
.replaces(AnalogInputCluster, endpoint_id=21)
.switch(
attribute_name="button_lock",
cluster_id=PlugAEU001Cluster.cluster_id,
force_inverted=True,
translation_key="button_lock",
fallback_name="Button Lock",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per HA spec, only the first letter should be capitalized, all other words should be lowercase (except for abbreviations). So, this should be "Buton lock", although we may want to use child_lock for the translation_key (and fallback_name if that is the same, as that's what we use for other devices).

Please also adjust the names of the other entities. (E.g. "Power on behavior", "LED indicator", ...) Thanks!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to child_lock and updated the other names as well. 👍

)
.enum(
attribute_name="power_on_behavior",
enum_class=AqaraPowerOutageMemoryEnum,
cluster_id=PlugAEU001Cluster.cluster_id,
translation_key="power_on_behavior",
fallback_name="Power On Behavior",
)
.number(
attribute_name="overload_protection",
cluster_id=PlugAEU001Cluster.cluster_id,
min_value=100,
max_value=3840,
unit=UnitOfPower.WATT,
translation_key="overload_protection",
fallback_name="Overload Protection",
)
.switch(
attribute_name="led_indicator",
cluster_id=PlugAEU001Cluster.cluster_id,
translation_key="led_indicator",
fallback_name="LED Indicator",
)
.switch(
attribute_name="charging_protection",
cluster_id=PlugAEU001Cluster.cluster_id,
translation_key="charging_protection",
fallback_name="Charging Protection",
)
.number(
attribute_name="charging_limit",
cluster_id=PlugAEU001Cluster.cluster_id,
min_value=0.1,
max_value=2,
step=0.1,
unit=UnitOfPower.WATT,
translation_key="charging_limit",
fallback_name="Charging Limit",
)
.add_to_registry()
)
Loading