Skip to content

Commit afd43e2

Browse files
cspurkdflemstr
authored andcommitted
Add quirk for Schneider Electric 1GANG/SHUTTER/1 (MEG5113-0300)
This commit is based on previous work by Axel Le Bot which unfortunately is incomplete and is unknown whether it works. I have hence removed the code that is not needed for the new quirk.
1 parent 97f56b5 commit afd43e2

File tree

7 files changed

+1794
-462
lines changed

7 files changed

+1794
-462
lines changed

tests/test_schneiderelectric.py

Lines changed: 67 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
1-
"""Tests for Schneider Electric."""
1+
"""Tests for Schneider Electric devices."""
2+
from unittest import mock
23

34
from zigpy.zcl import foundation
5+
from zigpy.zcl.clusters.closures import WindowCovering
46

57
import zhaquirks.schneiderelectric.devices.shutters
6-
import zhaquirks.schneiderelectric.devices.switches
78

8-
zhaquirks.setup()
9+
from tests.common import ClusterListener
910

10-
Default_Response = foundation.GENERAL_COMMANDS[
11-
foundation.GeneralCommand.Default_Response
12-
].schema
11+
zhaquirks.setup()
1312

1413

15-
def test_nhpb_shutter_1_signature(assert_signature_matches_quirk):
14+
def test_1gang_shutter_1_signature(assert_signature_matches_quirk):
1615
signature = {
17-
"node_descriptor": "NodeDescriptor(logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4190, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)",
16+
"node_descriptor": (
17+
"NodeDescriptor(logical_type=<LogicalType.Router: 1>, "
18+
"complex_descriptor_available=0, user_descriptor_available=0, reserved=0, "
19+
"aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, "
20+
"mac_capability_flags=<MACCapabilityFlags.FullFunctionDevice|MainsPowered"
21+
"|RxOnWhenIdle|AllocateAddress: 142>, manufacturer_code=4190, "
22+
"maximum_buffer_size=82, maximum_incoming_transfer_size=82, "
23+
"server_mask=10752, maximum_outgoing_transfer_size=82, "
24+
"descriptor_capability_field=<DescriptorCapability.NONE: 0>, "
25+
"*allocate_address=True, *is_alternate_pan_coordinator=False, "
26+
"*is_coordinator=False, *is_end_device=False, "
27+
"*is_full_function_device=True, *is_mains_powered=True, "
28+
"*is_receiver_on_when_idle=True, *is_router=True, "
29+
"*is_security_capable=False)"
30+
),
1831
"endpoints": {
1932
"5": {
20-
"profile_id": 260,
33+
"profile_id": 0x0104,
2134
"device_type": "0x0202",
2235
"in_clusters": [
2336
"0x0000",
@@ -30,188 +43,72 @@ def test_nhpb_shutter_1_signature(assert_signature_matches_quirk):
3043
"out_clusters": ["0x0019"],
3144
},
3245
"21": {
33-
"profile_id": 260,
46+
"profile_id": 0x0104,
3447
"device_type": "0x0104",
35-
"in_clusters": ["0x0000", "0x0003", "0x0b05", "0xff17"],
36-
"out_clusters": [
48+
"in_clusters": [
49+
"0x0000",
3750
"0x0003",
38-
"0x0004",
39-
"0x0005",
40-
"0x0006",
41-
"0x0008",
42-
"0x0102",
51+
"0x0b05",
52+
"0xff17",
4353
],
44-
},
45-
"242": {
46-
"profile_id": 41440,
47-
"device_type": "0x0061",
48-
"in_clusters": [],
49-
"out_clusters": ["0x0021"],
50-
},
51-
},
52-
"manufacturer": "Schneider Electric",
53-
"model": "NHPB/SHUTTER/1",
54-
"class": "zigpy.device.Device",
55-
}
56-
assert_signature_matches_quirk(
57-
zhaquirks.schneiderelectric.devices.shutters.NHPBShutter1, signature
58-
)
59-
60-
61-
def test_fls_air_link_4_signature(assert_signature_matches_quirk):
62-
signature = {
63-
"node_descriptor": "NodeDescriptor(logical_type=<LogicalType.EndDevice: 2>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress: 128>, manufacturer_code=4190, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=True, *is_full_function_device=False, *is_mains_powered=False, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False)",
64-
"endpoints": {
65-
"21": {
66-
"profile_id": 260,
67-
"device_type": "0x0104",
68-
"in_clusters": ["0x0000", "0x0001", "0x0003", "0x0020", "0xff17"],
6954
"out_clusters": [
7055
"0x0003",
71-
"0x0004",
7256
"0x0005",
7357
"0x0006",
7458
"0x0008",
7559
"0x0019",
7660
"0x0102",
7761
],
7862
},
79-
"22": {
80-
"profile_id": 260,
81-
"device_type": "0x0104",
82-
"in_clusters": ["0x0000", "0x0001", "0x0003", "0xff17"],
83-
"out_clusters": [
84-
"0x0003",
85-
"0x0004",
86-
"0x0005",
87-
"0x0006",
88-
"0x0008",
89-
"0x0102",
90-
],
91-
},
92-
"23": {
93-
"profile_id": 260,
94-
"device_type": "0x0104",
95-
"in_clusters": ["0x0000", "0x0001", "0x0003", "0xff17"],
96-
"out_clusters": [
97-
"0x0003",
98-
"0x0004",
99-
"0x0005",
100-
"0x0006",
101-
"0x0008",
102-
"0x0102",
103-
],
104-
},
105-
"24": {
106-
"profile_id": 260,
107-
"device_type": "0x0104",
108-
"in_clusters": ["0x0000", "0x0001", "0x0003", "0xff17"],
109-
"out_clusters": [
110-
"0x0003",
111-
"0x0004",
112-
"0x0005",
113-
"0x0006",
114-
"0x0008",
115-
"0x0102",
116-
],
117-
},
11863
},
11964
"manufacturer": "Schneider Electric",
120-
"model": "FLS/AIRLINK/4",
65+
"model": "1GANG/SHUTTER/1",
12166
"class": "zigpy.device.Device",
12267
}
12368
assert_signature_matches_quirk(
124-
zhaquirks.schneiderelectric.devices.switches.FLSAirlink4, signature
69+
zhaquirks.schneiderelectric.devices.shutters.OneGangShutter1, signature
12570
)
12671

12772

128-
def test_ch2ax_switch_1_signature(assert_signature_matches_quirk):
129-
signature = {
130-
"node_descriptor": "NodeDescriptor(logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4190, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)",
131-
"endpoints": {
132-
"1": {
133-
"profile_id": 260,
134-
"device_type": "0x0100",
135-
"in_clusters": [
136-
"0x0000",
137-
"0x0003",
138-
"0x0004",
139-
"0x0005",
140-
"0x0006",
141-
"0x0b05",
142-
],
143-
"out_clusters": ["0x0019"],
144-
},
145-
"21": {
146-
"profile_id": 260,
147-
"device_type": "0x0104",
148-
"in_clusters": ["0x0000", "0x0003", "0x0b05", "0xff17"],
149-
"out_clusters": [
150-
"0x0003",
151-
"0x0004",
152-
"0x0005",
153-
"0x0006",
154-
"0x0008",
155-
"0x0102",
156-
],
157-
},
158-
"242": {
159-
"profile_id": 41440,
160-
"device_type": "0x0061",
161-
"in_clusters": [],
162-
"out_clusters": ["0x0021"],
163-
},
164-
},
165-
"manufacturer": "Schneider Electric",
166-
"model": "CH2AX/SWITCH/1",
167-
"class": "zigpy.device.Device",
168-
}
169-
assert_signature_matches_quirk(
170-
zhaquirks.schneiderelectric.devices.switches.CHxAXSwitch1, signature
73+
async def test_1gang_shutter_1_go_to_lift_percentage_cmd(zigpy_device_from_quirk):
74+
"""Asserts that the go_to_lift_percentage command inverts the percentage value."""
75+
76+
device = zigpy_device_from_quirk(
77+
zhaquirks.schneiderelectric.devices.shutters.OneGangShutter1
17178
)
79+
window_covering_cluster = device.endpoints[5].window_covering
17280

81+
p = mock.patch.object(window_covering_cluster, "request", mock.AsyncMock())
82+
with p as request_mock:
83+
request_mock.return_value = (foundation.Status.SUCCESS, "done")
17384

174-
def test_ch10ax_switch_1_signature(assert_signature_matches_quirk):
175-
signature = {
176-
"node_descriptor": "NodeDescriptor(logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4190, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)",
177-
"endpoints": {
178-
"1": {
179-
"profile_id": 260,
180-
"device_type": "0x0100",
181-
"in_clusters": [
182-
"0x0000",
183-
"0x0003",
184-
"0x0004",
185-
"0x0005",
186-
"0x0006",
187-
"0x0b05",
188-
],
189-
"out_clusters": ["0x0019"],
190-
},
191-
"21": {
192-
"profile_id": 260,
193-
"device_type": "0x0104",
194-
"in_clusters": ["0x0000", "0x0003", "0x0b05", "0xff17"],
195-
"out_clusters": [
196-
"0x0003",
197-
"0x0004",
198-
"0x0005",
199-
"0x0006",
200-
"0x0008",
201-
"0x0102",
202-
],
203-
},
204-
"242": {
205-
"profile_id": 41440,
206-
"device_type": "0x0061",
207-
"in_clusters": [],
208-
"out_clusters": ["0x0021"],
209-
},
210-
},
211-
"manufacturer": "Schneider Electric",
212-
"model": "CH10AX/SWITCH/1",
213-
"class": "zigpy.device.Device",
214-
}
215-
assert_signature_matches_quirk(
216-
zhaquirks.schneiderelectric.devices.switches.CHxAXSwitch1, signature
85+
await window_covering_cluster.go_to_lift_percentage(58)
86+
87+
assert request_mock.call_count == 1
88+
assert request_mock.call_args[0][1] == (
89+
WindowCovering.ServerCommandDefs.go_to_lift_percentage.id
90+
)
91+
assert request_mock.call_args[0][3] == 42 # 100 - 58
92+
93+
94+
async def test_1gang_shutter_1_lift_percentage_updates(zigpy_device_from_quirk):
95+
"""Asserts that updates to the ``current_position_lift_percentage`` attribute
96+
(e.g., by the device) invert the reported percentage value."""
97+
98+
device = zigpy_device_from_quirk(
99+
zhaquirks.schneiderelectric.devices.shutters.OneGangShutter1
100+
)
101+
window_covering_cluster = device.endpoints[5].window_covering
102+
cluster_listener = ClusterListener(window_covering_cluster)
103+
104+
window_covering_cluster.update_attribute(
105+
WindowCovering.AttributeDefs.current_position_lift_percentage.id,
106+
77,
107+
)
108+
109+
assert len(cluster_listener.attribute_updates) == 1
110+
assert cluster_listener.attribute_updates[0] == (
111+
WindowCovering.AttributeDefs.current_position_lift_percentage.id,
112+
23, # 100 - 77
217113
)
114+
assert len(cluster_listener.cluster_commands) == 0

0 commit comments

Comments
 (0)