Skip to content

Commit bd6124a

Browse files
authored
Convert Schneider Electric shutters to quirks v2, expose entities (#4130)
- Convert to quirks v2 - Define missing attributes using spec doc - Add support for extra models (NHPB/SHUTTER/1 and PUCK/SHUTTER/1) - Expose extra entities
1 parent 363598d commit bd6124a

File tree

4 files changed

+209
-199
lines changed

4 files changed

+209
-199
lines changed

tests/test_schneiderelectric.py

Lines changed: 16 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -8,77 +8,19 @@
88
from zigpy.zcl.clusters.smartenergy import Metering
99

1010
from tests.common import ClusterListener
11+
from zhaquirks.schneiderelectric import SE_MANUF_NAME
1112
import zhaquirks.schneiderelectric.outlet
12-
import zhaquirks.schneiderelectric.shutters
1313

1414
zhaquirks.setup()
1515

1616

17-
def test_1gang_shutter_1_signature(assert_signature_matches_quirk):
18-
"""Test signature."""
19-
signature = {
20-
"node_descriptor": (
21-
"NodeDescriptor(logical_type=<LogicalType.Router: 1>, "
22-
"complex_descriptor_available=0, user_descriptor_available=0, reserved=0, "
23-
"aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, "
24-
"mac_capability_flags=<MACCapabilityFlags.FullFunctionDevice|MainsPowered"
25-
"|RxOnWhenIdle|AllocateAddress: 142>, manufacturer_code=4190, "
26-
"maximum_buffer_size=82, maximum_incoming_transfer_size=82, "
27-
"server_mask=10752, maximum_outgoing_transfer_size=82, "
28-
"descriptor_capability_field=<DescriptorCapability.NONE: 0>, "
29-
"*allocate_address=True, *is_alternate_pan_coordinator=False, "
30-
"*is_coordinator=False, *is_end_device=False, "
31-
"*is_full_function_device=True, *is_mains_powered=True, "
32-
"*is_receiver_on_when_idle=True, *is_router=True, "
33-
"*is_security_capable=False)"
34-
),
35-
"endpoints": {
36-
"5": {
37-
"profile_id": 0x0104,
38-
"device_type": "0x0202",
39-
"in_clusters": [
40-
"0x0000",
41-
"0x0003",
42-
"0x0004",
43-
"0x0005",
44-
"0x0102",
45-
"0x0b05",
46-
],
47-
"out_clusters": ["0x0019"],
48-
},
49-
"21": {
50-
"profile_id": 0x0104,
51-
"device_type": "0x0104",
52-
"in_clusters": [
53-
"0x0000",
54-
"0x0003",
55-
"0x0b05",
56-
"0xff17",
57-
],
58-
"out_clusters": [
59-
"0x0003",
60-
"0x0005",
61-
"0x0006",
62-
"0x0008",
63-
"0x0019",
64-
"0x0102",
65-
],
66-
},
67-
},
68-
"manufacturer": "Schneider Electric",
69-
"model": "1GANG/SHUTTER/1",
70-
"class": "zigpy.device.Device",
71-
}
72-
assert_signature_matches_quirk(
73-
zhaquirks.schneiderelectric.shutters.OneGangShutter1, signature
74-
)
75-
76-
77-
async def test_1gang_shutter_1_go_to_lift_percentage_cmd(zigpy_device_from_quirk):
17+
async def test_1gang_shutter_1_go_to_lift_percentage_cmd(zigpy_device_from_v2_quirk):
7818
"""Asserts that the go_to_lift_percentage command inverts the percentage value."""
7919

80-
device = zigpy_device_from_quirk(
81-
zhaquirks.schneiderelectric.shutters.OneGangShutter1
20+
device = zigpy_device_from_v2_quirk(
21+
manufacturer=SE_MANUF_NAME,
22+
model="1GANG/SHUTTER/1",
23+
endpoint_ids=[5, 21],
8224
)
8325
window_covering_cluster = device.endpoints[5].window_covering
8426

@@ -95,11 +37,13 @@ async def test_1gang_shutter_1_go_to_lift_percentage_cmd(zigpy_device_from_quirk
9537
assert request_mock.call_args[0][3] == 42 # 100 - 58
9638

9739

98-
async def test_1gang_shutter_1_unpatched_cmd(zigpy_device_from_quirk):
40+
async def test_1gang_shutter_1_unpatched_cmd(zigpy_device_from_v2_quirk):
9941
"""Asserts that unpatched ZCL commands keep working."""
10042

101-
device = zigpy_device_from_quirk(
102-
zhaquirks.schneiderelectric.shutters.OneGangShutter1
43+
device = zigpy_device_from_v2_quirk(
44+
manufacturer=SE_MANUF_NAME,
45+
model="1GANG/SHUTTER/1",
46+
endpoint_ids=[5, 21],
10347
)
10448
window_covering_cluster = device.endpoints[5].window_covering
10549

@@ -115,14 +59,16 @@ async def test_1gang_shutter_1_unpatched_cmd(zigpy_device_from_quirk):
11559
)
11660

11761

118-
async def test_1gang_shutter_1_lift_percentage_updates(zigpy_device_from_quirk):
62+
async def test_1gang_shutter_1_lift_percentage_updates(zigpy_device_from_v2_quirk):
11963
"""Asserts that updates to the ``current_position_lift_percentage`` attribute.
12064
12165
(e.g., by the device) invert the reported percentage value.
12266
"""
12367

124-
device = zigpy_device_from_quirk(
125-
zhaquirks.schneiderelectric.shutters.OneGangShutter1
68+
device = zigpy_device_from_v2_quirk(
69+
manufacturer=SE_MANUF_NAME,
70+
model="1GANG/SHUTTER/1",
71+
endpoint_ids=[5, 21],
12672
)
12773
window_covering_cluster = device.endpoints[5].window_covering
12874
cluster_listener = ClusterListener(window_covering_cluster)

zhaquirks/schneiderelectric/__init__.py

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ class SEDimmingCurve(t.enum8):
155155
Exponential = 2 # Not supported in current FW, but defined in the spec
156156

157157

158+
class SESunProtectionStatus(t.bitmap8):
159+
"""Window covering protetion status."""
160+
161+
NotActive = 0
162+
Active = 1
163+
164+
165+
class SESunProtectionSensor(t.bitmap8):
166+
"""Window covering protetion status."""
167+
168+
NotConnected = 0
169+
Connected = 1
170+
171+
158172
class SEBallast(CustomCluster, Ballast):
159173
"""Schneider Electric Ballast cluster."""
160174

@@ -180,49 +194,85 @@ class SEWindowCovering(CustomCluster, WindowCovering):
180194
class AttributeDefs(WindowCovering.AttributeDefs):
181195
"""Attribute definitions."""
182196

183-
unknown_attribute_65533: Final = ZCLAttributeDef(
184-
id=0xFFFD,
185-
type=t.uint16_t,
186-
is_manufacturer_specific=True,
187-
)
188-
lift_duration: Final = ZCLAttributeDef(
197+
# Obsolete, use se_lift_drive_up_time and se_lift_drive_down_time instead.
198+
se_lift_duration: Final = ZCLAttributeDef(
189199
id=0xE000,
190200
type=t.uint16_t,
201+
access="rw",
191202
is_manufacturer_specific=True,
192203
)
193-
unknown_attribute_57360: Final = ZCLAttributeDef(
204+
se_sun_protection_status: Final = ZCLAttributeDef(
194205
id=0xE010,
195-
type=t.bitmap8,
206+
type=SESunProtectionStatus,
207+
access="r",
196208
is_manufacturer_specific=True,
197209
)
198-
unknown_attribute_57362: Final = ZCLAttributeDef(
210+
# SunProtectionIlluminanceThreshold = 10,000 x log10 Illuminance.
211+
# Where 1 lx <= Illuminance <=3.576 Mlx, corresponding to a SunProtectionIlluminanceThreshold in the range 0 to 0xfffe.
212+
# A value of 0xffff indicates that this attribute is not valid.
213+
se_sun_protection_illuminance_thershold: Final = ZCLAttributeDef(
199214
id=0xE012,
200215
type=t.uint16_t,
216+
access="rw",
201217
is_manufacturer_specific=True,
202218
)
203-
unknown_attribute_57363: Final = ZCLAttributeDef(
219+
se_sun_protection_sensor: Final = ZCLAttributeDef(
204220
id=0xE013,
205-
type=t.bitmap8,
221+
type=SESunProtectionSensor,
222+
access="rw",
206223
is_manufacturer_specific=True,
207224
)
208-
unknown_attribute_57364: Final = ZCLAttributeDef(
225+
# Driving time from fully close to fully open state in 1/10 seconds
226+
se_lift_drive_up_time: Final = ZCLAttributeDef(
209227
id=0xE014,
210228
type=t.uint16_t,
229+
access="rw",
211230
is_manufacturer_specific=True,
212231
)
213-
unknown_attribute_57365: Final = ZCLAttributeDef(
232+
# Driving time from fully open to fully close state in 1/10 seconds
233+
se_lift_drive_down_time: Final = ZCLAttributeDef(
214234
id=0xE015,
215235
type=t.uint16_t,
236+
access="rw",
216237
is_manufacturer_specific=True,
217238
)
218-
unknown_attribute_57366: Final = ZCLAttributeDef(
239+
# Time from fully open to fully close tilt position in 1/100 seconds.
240+
# This time is also taken as base for calculation of step size in Schneider manufacture specific command StopOrStepLiftPercentage.
241+
# If set to 0, WindosCoveringType attribute is automatically set to 0 (lift only).
242+
se_tilt_open_close_and_step_time: Final = ZCLAttributeDef(
219243
id=0xE016,
220244
type=t.uint16_t,
245+
access="rw",
221246
is_manufacturer_specific=True,
222247
)
223-
unknown_attribute_57367: Final = ZCLAttributeDef(
248+
# Tilt position in percent adopted by tilt after receiving go to lift percentage command.
249+
# Values 0-100 are absolute position of tilt with following meaning:
250+
# 100: Position of tilt when shutter is moving up (usually up).
251+
# 0: Position of tilt when shutter is moving down (usually down).
252+
# 255: No action after command.
253+
# 101-254: Tilt position before movement is restored.
254+
se_tilt_position_percentage_after_move_to_level: Final = ZCLAttributeDef(
224255
id=0xE017,
225256
type=t.uint8_t,
257+
access="rw",
258+
is_manufacturer_specific=True,
259+
)
260+
se_cluster_revision: Final = ZCLAttributeDef(
261+
id=0xFFFD,
262+
type=t.uint16_t,
263+
access="r",
264+
is_manufacturer_specific=True,
265+
)
266+
267+
class ServerCommandDefs(WindowCovering.ServerCommandDefs):
268+
"""Server command definitions."""
269+
270+
se_stop_or_step_lift_percentage: Final = foundation.ZCLCommandDef(
271+
id=0x80,
272+
schema={
273+
"direction": t.uint8_t,
274+
"step_value": t.uint8_t,
275+
},
226276
is_manufacturer_specific=True,
227277
)
228278

@@ -305,11 +355,11 @@ class SESwitchAction(t.enum8):
305355
NotUsed = 0x7F
306356

307357

308-
class SESpecific(CustomCluster):
309-
"""Schneider Electric manufacturer specific cluster."""
358+
class SESwitchConfiguration(CustomCluster):
359+
"""Schneider Electric manufacturer specific switch configuration cluster."""
310360

311-
name = "Schneider Electric Manufacturer Specific"
312-
ep_attribute = "schneider_electric_manufacturer"
361+
name = "Schneider Electric Manufacturer Specific Switch Configuration"
362+
ep_attribute = "switch_configuration"
313363
cluster_id = 0xFF17
314364

315365
class AttributeDefs(BaseAttributeDefs):

zhaquirks/schneiderelectric/dimmers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
SEBallast,
88
SEBasic,
99
SEOnOff,
10-
SESpecific,
10+
SESwitchConfiguration,
1111
)
1212

1313
(
@@ -19,7 +19,7 @@
1919
.replaces(SEBallast, endpoint_id=3)
2020
.replaces(SEOnOff, endpoint_id=3)
2121
.replaces(SEBasic, endpoint_id=21)
22-
.replaces(SESpecific, endpoint_id=21)
22+
.replaces(SESwitchConfiguration, endpoint_id=21)
2323
.add_to_registry()
2424
)
2525

@@ -29,6 +29,6 @@
2929
.replaces(SEBasic)
3030
.replaces(SEOnOff)
3131
.replaces(SEBasic, endpoint_id=21)
32-
.replaces(SESpecific, endpoint_id=21)
32+
.replaces(SESwitchConfiguration, endpoint_id=21)
3333
.add_to_registry()
3434
)

0 commit comments

Comments
 (0)