Skip to content

Commit 83057c4

Browse files
committed
Merge branch 'release/0.0.67'
2 parents 53e33ae + 0785124 commit 83057c4

29 files changed

+2576
-101
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ build/**/*.*
88
.tox
99
.idea/*
1010
.coverage
11+
.mypycache

.pre-commit-config.yaml

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
repos:
2+
- repo: https://github.com/asottile/pyupgrade
3+
rev: v2.31.0
4+
hooks:
5+
- id: pyupgrade
6+
7+
- repo: https://github.com/fsouza/autoflake8
8+
rev: v0.3.1
9+
hooks:
10+
- id: autoflake8
11+
212
- repo: https://github.com/psf/black
313
rev: 20.8b1
414
hooks:
@@ -21,10 +31,11 @@ repos:
2131
- id: isort
2232

2333
- repo: https://github.com/codespell-project/codespell
24-
rev: v1.17.1
34+
rev: v2.1.0
2535
hooks:
2636
- id: codespell
27-
args:
28-
- --ignore-words-list=dout,hass
29-
- --skip="./.*"
30-
- --quiet-level=2
37+
38+
- repo: https://github.com/pre-commit/mirrors-mypy
39+
rev: v0.902
40+
hooks:
41+
- id: mypy

setup.cfg

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
testpaths = tests
33
norecursedirs = .git testing_config
44

5+
[autoflake8]
6+
in-place = True
7+
recursive = False
8+
expand-star-imports = False
9+
exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build
10+
511
[flake8]
612
exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build
713
# To work with Black
@@ -53,9 +59,25 @@ warn_return_any = true
5359
warn_unreachable = true
5460
warn_unused_configs = true
5561
warn_unused_ignores = true
62+
show_error_codes = True
63+
show_error_context = True
64+
error_summary = True
65+
66+
install_types = True
67+
non_interactive = True
5668

5769
[pydocstyle]
5870
ignore =
5971
D202,
6072
D203,
6173
D213
74+
75+
[pyupgrade]
76+
py37plus = True
77+
78+
[codespell]
79+
exclude = setup.cfg
80+
ignore-words-list = hass,dout
81+
skip = ./.*,test/*
82+
quiet-level = 2
83+

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from setuptools import find_packages, setup
44

5-
VERSION = "0.0.66"
5+
VERSION = "0.0.67"
66

77

88
def readme():

tests/test_tuya.py

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,22 @@
6161
ZCL_TUYA_VALVE_AUTO_LOCK_ON = b"\t2\x01\x03\x04\x74\x01\x00\x01\x01"
6262
ZCL_TUYA_VALVE_BATTERY_LOW = b"\t2\x01\x03\x04\x6E\x01\x00\x01\x01"
6363

64+
ZCL_TUYA_VALVE_ZONNSMART_TEMPERATURE = (
65+
b"\tp\x01\x00\x02\x18\x02\x00\x04\x00\x00\x00\xd3"
66+
)
67+
ZCL_TUYA_VALVE_ZONNSMART_TARGET_TEMP = (
68+
b"\t3\x01\x03\x05\x10\x02\x00\x04\x00\x00\x00\xcd"
69+
)
70+
ZCL_TUYA_VALVE_ZONNSMART_HOLIDAY_TEMP = (
71+
b"\t3\x01\x03\x05\x20\x02\x00\x04\x00\x00\x00\xaa"
72+
)
73+
ZCL_TUYA_VALVE_ZONNSMART_TEMP_OFFSET = (
74+
b"\t3\x01\x03\x05\x1b\x02\x00\x04\x00\x00\x00\x0b"
75+
)
76+
ZCL_TUYA_VALVE_ZONNSMART_MODE_MANUAL = b"\t2\x01\x03\x04\x02\x04\x00\x01\x01"
77+
ZCL_TUYA_VALVE_ZONNSMART_MODE_SCHEDULE = b"\t2\x01\x03\x04\x02\x04\x00\x01\x00"
78+
ZCL_TUYA_VALVE_ZONNSMART_HEAT_STOP = b"\t2\x01\x03\x04\x6b\x01\x00\x01\x00"
79+
6480
ZCL_TUYA_EHEAT_TEMPERATURE = b"\tp\x02\x00\x02\x18\x02\x00\x04\x00\x00\x00\xb3"
6581
ZCL_TUYA_EHEAT_TARGET_TEMP = b"\t3\x01\x03\x05\x10\x02\x00\x04\x00\x00\x00\x15"
6682

@@ -223,8 +239,8 @@ async def test_singleswitch_requests(zigpy_device_from_quirk, quirk):
223239
status = await switch_cluster.command(0x0000)
224240
m1.assert_called_with(
225241
61184,
226-
1,
227-
b"\x01\x01\x00\x00\x00\x01\x01\x00\x01\x00",
242+
2,
243+
b"\x01\x02\x00\x00\x01\x01\x01\x00\x01\x00",
228244
expect_reply=True,
229245
command_id=0,
230246
)
@@ -233,8 +249,8 @@ async def test_singleswitch_requests(zigpy_device_from_quirk, quirk):
233249
status = await switch_cluster.command(0x0001)
234250
m1.assert_called_with(
235251
61184,
236-
2,
237-
b"\x01\x02\x00\x00\x00\x01\x01\x00\x01\x01",
252+
4,
253+
b"\x01\x04\x00\x00\x03\x01\x01\x00\x01\x01",
238254
expect_reply=True,
239255
command_id=0,
240256
)
@@ -408,6 +424,130 @@ async def async_success(*args, **kwargs):
408424
assert status == foundation.Status.UNSUP_CLUSTER_COMMAND
409425

410426

427+
@pytest.mark.parametrize("quirk", (zhaquirks.tuya.ts0601_trv.ZonnsmartTV01_ZG,))
428+
async def test_zonnsmart_state_report(zigpy_device_from_quirk, quirk):
429+
"""Test thermostatic valves standard reporting from incoming commands."""
430+
431+
valve_dev = zigpy_device_from_quirk(quirk)
432+
tuya_cluster = valve_dev.endpoints[1].tuya_manufacturer
433+
434+
thermostat_listener = ClusterListener(valve_dev.endpoints[1].thermostat)
435+
436+
frames = (
437+
ZCL_TUYA_VALVE_ZONNSMART_TEMPERATURE,
438+
ZCL_TUYA_VALVE_ZONNSMART_TARGET_TEMP,
439+
ZCL_TUYA_VALVE_ZONNSMART_HOLIDAY_TEMP,
440+
ZCL_TUYA_VALVE_ZONNSMART_TEMP_OFFSET,
441+
ZCL_TUYA_VALVE_ZONNSMART_MODE_MANUAL,
442+
ZCL_TUYA_VALVE_ZONNSMART_MODE_SCHEDULE,
443+
ZCL_TUYA_VALVE_ZONNSMART_HEAT_STOP,
444+
)
445+
for frame in frames:
446+
hdr, args = tuya_cluster.deserialize(frame)
447+
tuya_cluster.handle_message(hdr, args)
448+
449+
assert len(thermostat_listener.cluster_commands) == 0
450+
assert len(thermostat_listener.attribute_updates) == 11
451+
assert thermostat_listener.attribute_updates[0][0] == 0x0000 # TEMP
452+
assert thermostat_listener.attribute_updates[0][1] == 2110
453+
assert thermostat_listener.attribute_updates[1][0] == 0x0012 # TARGET
454+
assert thermostat_listener.attribute_updates[1][1] == 2050
455+
assert thermostat_listener.attribute_updates[4][0] == 0x0014 # HOLIDAY
456+
assert thermostat_listener.attribute_updates[4][1] == 1700
457+
assert thermostat_listener.attribute_updates[5][0] == 0x0010 # OFFSET
458+
assert thermostat_listener.attribute_updates[5][1] == 110
459+
assert thermostat_listener.attribute_updates[6][0] == 0x0025 # MANUAL
460+
assert thermostat_listener.attribute_updates[6][1] == 0
461+
assert thermostat_listener.attribute_updates[7][0] == 0x4002
462+
assert thermostat_listener.attribute_updates[7][1] == 1
463+
assert thermostat_listener.attribute_updates[8][0] == 0x0025 # SCHEDULE
464+
assert thermostat_listener.attribute_updates[8][1] == 1
465+
assert thermostat_listener.attribute_updates[9][0] == 0x4002
466+
assert thermostat_listener.attribute_updates[9][1] == 0
467+
assert thermostat_listener.attribute_updates[10][0] == 0x001C # HEAT ON
468+
assert thermostat_listener.attribute_updates[10][1] == 4
469+
470+
471+
@pytest.mark.parametrize("quirk", (zhaquirks.tuya.ts0601_trv.ZonnsmartTV01_ZG,))
472+
async def test_zonnsmart_send_attribute(zigpy_device_from_quirk, quirk):
473+
"""Test thermostatic valve outgoing commands."""
474+
475+
valve_dev = zigpy_device_from_quirk(quirk)
476+
tuya_cluster = valve_dev.endpoints[1].tuya_manufacturer
477+
thermostat_cluster = valve_dev.endpoints[1].thermostat
478+
479+
async def async_success(*args, **kwargs):
480+
return foundation.Status.SUCCESS
481+
482+
with mock.patch.object(
483+
tuya_cluster.endpoint, "request", side_effect=async_success
484+
) as m1:
485+
486+
(status,) = await thermostat_cluster.write_attributes(
487+
{
488+
"occupied_heating_setpoint": 2500,
489+
}
490+
)
491+
m1.assert_called_with(
492+
61184,
493+
1,
494+
b"\x01\x01\x00\x00\x01\x10\x02\x00\x04\x00\x00\x00\xfa",
495+
expect_reply=False,
496+
command_id=0,
497+
)
498+
assert status == [
499+
foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)
500+
]
501+
502+
(status,) = await thermostat_cluster.write_attributes(
503+
{
504+
"operation_preset": 1,
505+
}
506+
)
507+
m1.assert_called_with(
508+
61184,
509+
2,
510+
b"\x01\x02\x00\x00\x02\x02\x04\x00\x01\x01",
511+
expect_reply=False,
512+
command_id=0,
513+
)
514+
assert status == [
515+
foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)
516+
]
517+
518+
(status,) = await thermostat_cluster.write_attributes(
519+
{
520+
"operation_preset": 4, # frost protection wrapped as operation_preset
521+
}
522+
)
523+
m1.assert_called_with(
524+
61184,
525+
3,
526+
b"\x01\x03\x00\x00\x03\x0a\x01\x00\x01\x01",
527+
expect_reply=False,
528+
command_id=0,
529+
)
530+
assert status == [
531+
foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)
532+
]
533+
534+
(status,) = await thermostat_cluster.write_attributes(
535+
{
536+
"system_mode": 0, # SystemMode.Off
537+
}
538+
)
539+
m1.assert_called_with(
540+
61184,
541+
4,
542+
b"\x01\x04\x00\x00\x04\x6b\x01\x00\x01\x01",
543+
expect_reply=False,
544+
command_id=0,
545+
)
546+
assert status == [
547+
foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)
548+
]
549+
550+
411551
@pytest.mark.parametrize("quirk", (zhaquirks.tuya.ts0601_trv.SiterwellGS361_Type1,))
412552
async def test_valve_state_report(zigpy_device_from_quirk, quirk):
413553
"""Test thermostatic valves standard reporting from incoming commands."""

tests/test_tuya_dimmer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ async def async_success(*args, **kwargs):
7979
61184,
8080
2,
8181
b"\x01\x02\x00\x00\x01\x03\x02\x00\x04\x00\x00\x00b",
82-
expect_reply=True,
82+
expect_reply=False,
8383
command_id=0,
8484
)
8585
assert status == [

tests/test_tuya_mcu.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ async def test_tuya_methods(zigpy_device_from_quirk, quirk):
6363

6464
tcd_1 = TuyaClusterData(endpoint_id=2, cluster_attr="minimum_level", attr_value=25)
6565

66-
tcd_switch1_on = TuyaClusterData(endpoint_id=1, cluster_attr="on_off", attr_value=1)
66+
tcd_switch1_on = TuyaClusterData(
67+
endpoint_id=1, cluster_attr="on_off", attr_value=1, expect_reply=True
68+
)
6769

6870
result_1 = tuya_cluster.from_cluster_data(tcd_1)
6971
assert result_1

zhaquirks/innr/innr_sp234_plug.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""Innr SP 234 plug."""
2+
from zigpy.profiles import zha
3+
from zigpy.quirks import CustomCluster, CustomDevice
4+
from zigpy.zcl.clusters.general import (
5+
Basic,
6+
GreenPowerProxy,
7+
Groups,
8+
Identify,
9+
OnOff,
10+
Ota,
11+
Scenes,
12+
Time,
13+
)
14+
from zigpy.zcl.clusters.homeautomation import Diagnostic, ElectricalMeasurement
15+
from zigpy.zcl.clusters.lightlink import LightLink
16+
from zigpy.zcl.clusters.smartenergy import Metering
17+
18+
from zhaquirks.const import (
19+
DEVICE_TYPE,
20+
ENDPOINTS,
21+
INPUT_CLUSTERS,
22+
MODELS_INFO,
23+
OUTPUT_CLUSTERS,
24+
PROFILE_ID,
25+
)
26+
27+
MANUFACTURER = "innr"
28+
MODEL = "SP 234"
29+
30+
31+
class ElectricalMeasurementCluster(CustomCluster, ElectricalMeasurement):
32+
"""Fix divisor."""
33+
34+
cluster_id = ElectricalMeasurement.cluster_id
35+
AC_POWER_DIVISOR = 0x0605
36+
_CONSTANT_ATTRIBUTES = {AC_POWER_DIVISOR: 1}
37+
38+
39+
class SP234(CustomDevice):
40+
"""Innr SP 234 smart plug."""
41+
42+
signature = {
43+
ENDPOINTS: {
44+
1: {
45+
PROFILE_ID: zha.PROFILE_ID,
46+
DEVICE_TYPE: zha.DeviceType.ON_OFF_PLUG_IN_UNIT,
47+
INPUT_CLUSTERS: [
48+
Basic.cluster_id,
49+
Identify.cluster_id,
50+
Groups.cluster_id,
51+
Scenes.cluster_id,
52+
OnOff.cluster_id,
53+
Metering.cluster_id,
54+
ElectricalMeasurement.cluster_id,
55+
Diagnostic.cluster_id,
56+
LightLink.cluster_id,
57+
0xFC57,
58+
0xFC82,
59+
],
60+
OUTPUT_CLUSTERS: [
61+
Identify.cluster_id,
62+
Time.cluster_id,
63+
Ota.cluster_id,
64+
],
65+
},
66+
242: {
67+
PROFILE_ID: 41440,
68+
DEVICE_TYPE: 0x0061,
69+
INPUT_CLUSTERS: [],
70+
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
71+
},
72+
},
73+
MODELS_INFO: [(MANUFACTURER, MODEL)],
74+
}
75+
76+
replacement = {
77+
ENDPOINTS: {
78+
1: {
79+
PROFILE_ID: zha.PROFILE_ID,
80+
DEVICE_TYPE: zha.DeviceType.ON_OFF_PLUG_IN_UNIT,
81+
INPUT_CLUSTERS: [
82+
Basic.cluster_id,
83+
Identify.cluster_id,
84+
Groups.cluster_id,
85+
Scenes.cluster_id,
86+
OnOff.cluster_id,
87+
Metering.cluster_id,
88+
ElectricalMeasurementCluster,
89+
Diagnostic.cluster_id,
90+
LightLink.cluster_id,
91+
0xFC57,
92+
0xFC82,
93+
],
94+
OUTPUT_CLUSTERS: [
95+
Identify.cluster_id,
96+
Time.cluster_id,
97+
Ota.cluster_id,
98+
],
99+
},
100+
242: {
101+
PROFILE_ID: 41440,
102+
DEVICE_TYPE: 0x0061,
103+
INPUT_CLUSTERS: [],
104+
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
105+
},
106+
},
107+
}

0 commit comments

Comments
 (0)