Skip to content

Commit d55ec7c

Browse files
committed
Create SONOFF S60ZBTPF quirk
The power and current values of the SONOFF S60ZBTPF and S60ZBTPG are not correctly reset to zero when the switch of the socket is turned off. Similarly, these devices sometimes even send power and current updates even after the switch was turned off. This commit creates a custom quirk for the two devices to fix the broken behavior. I've only tested this with the SONOFF S60ZBTPF devices I have, but, judging by https://github.com/Koenkk/zigbee-herdsman-converters/blob/v25.44.0/src/devices/sonoff.ts#L2020, this should also apply to the S60ZBTPG.
1 parent 02fe2c8 commit d55ec7c

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

tests/test_sonoff_s60zbtpf.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Tests for the SONOFF S60ZBTPF device."""
2+
3+
import pytest
4+
from zigpy.zcl import ClusterType
5+
from zigpy.zcl.clusters.general import OnOff
6+
from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
7+
8+
import zhaquirks.sonoff.s60zbtpf
9+
10+
zhaquirks.setup()
11+
12+
POWER_ID = ElectricalMeasurement.AttributeDefs.active_power.id
13+
CURRENT_ID = ElectricalMeasurement.AttributeDefs.rms_current.id
14+
ON_OFF_ID = OnOff.AttributeDefs.on_off.id
15+
16+
17+
@pytest.fixture
18+
def device(zigpy_device_from_v2_quirk):
19+
"""Create a SONOFF S60ZBTPF zigpy device mock for testing."""
20+
cluster_ids = {1: {ElectricalMeasurement.cluster_id: ClusterType.Server}}
21+
return zigpy_device_from_v2_quirk("SONOFF", "S60ZBTPF", cluster_ids=cluster_ids)
22+
23+
24+
async def test_power_fix(device):
25+
"""Test power measurement overrides."""
26+
electrical_cluster = device.endpoints[1].electrical_measurement
27+
on_off_cluster = device.endpoints[1].on_off
28+
29+
electrical_cluster.update_attribute(POWER_ID, 300)
30+
electrical_cluster.update_attribute(CURRENT_ID, 13)
31+
assert electrical_cluster.get(POWER_ID) == 300
32+
assert electrical_cluster.get(CURRENT_ID) == 13
33+
34+
on_off_cluster.update_attribute(ON_OFF_ID, False)
35+
assert electrical_cluster.get(POWER_ID) == 0
36+
assert electrical_cluster.get(CURRENT_ID) == 0
37+
38+
electrical_cluster.update_attribute(POWER_ID, 300)
39+
electrical_cluster.update_attribute(CURRENT_ID, 13)
40+
assert electrical_cluster.get(POWER_ID) == 0
41+
assert electrical_cluster.get(CURRENT_ID) == 0
42+
43+
on_off_cluster.update_attribute(ON_OFF_ID, True)
44+
assert electrical_cluster.get(POWER_ID) == 0
45+
assert electrical_cluster.get(CURRENT_ID) == 0
46+
47+
electrical_cluster.update_attribute(POWER_ID, 300)
48+
electrical_cluster.update_attribute(CURRENT_ID, 13)
49+
assert electrical_cluster.get(POWER_ID) == 300
50+
assert electrical_cluster.get(CURRENT_ID) == 13

zhaquirks/sonoff/s60zbtpf.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""SONOFF S60ZBTPF - Smart Socket with power measurement fix.
2+
3+
This device has a quirk where it continues to report active power consumption
4+
even when the socket is turned off. This quirk fixes that by setting the
5+
`active_power` and `rms_current` to 0 when the `on_off` state is False.
6+
"""
7+
8+
from zigpy.quirks import CustomCluster
9+
from zigpy.quirks.v2 import QuirkBuilder
10+
import zigpy.types as t
11+
from zigpy.zcl.clusters.general import OnOff
12+
from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
13+
14+
POWER_ID = ElectricalMeasurement.AttributeDefs.active_power.id
15+
CURRENT_ID = ElectricalMeasurement.AttributeDefs.rms_current.id
16+
VOLTAGE_ID = ElectricalMeasurement.AttributeDefs.rms_voltage.id
17+
ON_OFF_ID = OnOff.AttributeDefs.on_off.id
18+
19+
20+
class SonoffS60OnOff(CustomCluster, OnOff):
21+
"""Custom OnOff cluster that resets power readings when the socket is turned off."""
22+
23+
def _update_attribute(self, attrid, value):
24+
"""Reset attributes to zero when the socket is turned off."""
25+
26+
if attrid == ON_OFF_ID and value == t.Bool.false:
27+
self.debug(
28+
"Socket turned off, resetting power and current measurements to zero"
29+
)
30+
self.endpoint.electrical_measurement.update_attribute(POWER_ID, 0)
31+
self.endpoint.electrical_measurement.update_attribute(CURRENT_ID, 0)
32+
self.endpoint.electrical_measurement.update_attribute(VOLTAGE_ID, None)
33+
34+
super()._update_attribute(attrid, value)
35+
36+
37+
class SonoffS60ElectricalMeasurement(CustomCluster, ElectricalMeasurement):
38+
"""Custom ElectricalMeasurement cluster that prevents power updates when the socket is turned off."""
39+
40+
def _update_attribute(self, attrid, value):
41+
"""Prevent updates when the socket is turned off."""
42+
43+
if self.endpoint.on_off.get(ON_OFF_ID) == t.Bool.false:
44+
if attrid == POWER_ID:
45+
self.debug("Socket turned off, preventing power measurement update")
46+
return
47+
48+
if attrid == CURRENT_ID:
49+
self.debug("Socket turned off, preventing current measurement update")
50+
return
51+
52+
if attrid == VOLTAGE_ID:
53+
self.debug("Socket turned off, preventing voltage measurement update")
54+
return
55+
56+
super()._update_attribute(attrid, value)
57+
58+
59+
(
60+
QuirkBuilder("SONOFF", "S60ZBTPF")
61+
.also_applies_to("SONOFF", "S60ZBTPG")
62+
.replaces(SonoffS60OnOff)
63+
.replaces(SonoffS60ElectricalMeasurement)
64+
.add_to_registry()
65+
)

0 commit comments

Comments
 (0)