Skip to content

Commit c4e2a1c

Browse files
TheJulianJESpuddly
andauthored
Bump zigpy to 0.80.0 (#4088)
* Bump zigpy to 0.80.0 * Fix tests not replacing `hdr.frame_control` * Fixed unrelated Tuya `return` issue * Make `TuyaData` a custom object, it's not a struct * Ensure the command direction is specified for XBee "ZCL" commands * Fix missing kwarg in `TuyaData` initializer --------- Co-authored-by: puddly <[email protected]>
1 parent f9898ee commit c4e2a1c

File tree

5 files changed

+64
-29
lines changed

5 files changed

+64
-29
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ readme = "README.md"
1414
license = {text = "Apache-2.0"}
1515
requires-python = ">=3.12"
1616
dependencies = [
17-
"zigpy>=0.76.0",
17+
"zigpy>=0.80.0",
1818
]
1919

2020
[tool.setuptools.packages.find]

requirements_test.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ pytest-sugar
1313
pytest-timeout
1414
pytest-asyncio
1515
pytest>=7.1.3
16-
zigpy>=0.76.0
16+
zigpy>=0.80.0
1717
ruff==0.0.261

tests/test_tuya_clusters.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def test_tuya_cluster_request(
252252
"""Test cluster specific request."""
253253

254254
hdr = zcl_f.ZCLHeader.general(1, cmd_id, direction=zcl_f.Direction.Server_to_Client)
255-
hdr.frame_control.disable_default_response = False
255+
hdr.frame_control = hdr.frame_control.replace(disable_default_response=False)
256256

257257
with mock.patch.object(TuyaCluster, handler_name) as handler:
258258
handler.return_value = mock.sentinel.status
@@ -267,7 +267,7 @@ def test_tuya_cluster_request_unk_command(default_rsp_mock, TuyaCluster):
267267
"""Test cluster specific request handler -- no handler."""
268268

269269
hdr = zcl_f.ZCLHeader.general(1, 0xFE, direction=zcl_f.Direction.Server_to_Client)
270-
hdr.frame_control.disable_default_response = False
270+
hdr.frame_control = hdr.frame_control.replace(disable_default_response=False)
271271

272272
TuyaCluster.handle_cluster_request(hdr, (mock.sentinel.args,))
273273
assert default_rsp_mock.call_count == 1
@@ -279,7 +279,7 @@ def test_tuya_cluster_request_no_handler(default_rsp_mock, TuyaCluster):
279279
"""Test cluster specific request handler -- no handler."""
280280

281281
hdr = zcl_f.ZCLHeader.general(1, 0xFE, direction=zcl_f.Direction.Server_to_Client)
282-
hdr.frame_control.disable_default_response = False
282+
hdr.frame_control = hdr.frame_control.replace(disable_default_response=False)
283283

284284
new_client_commands = TuyaCluster.client_commands.copy()
285285
new_client_commands[0xFE] = zcl_f.ZCLCommandDef(

zhaquirks/tuya/__init__.py

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,41 @@ class TuyaDPType(t.enum8):
148148
BITMAP = 0x05
149149

150150

151-
class TuyaData(t.Struct):
151+
class TuyaData:
152152
"""Tuya Data type."""
153153

154154
dp_type: TuyaDPType
155155
function: t.uint8_t
156156
raw: t.LVBytes
157157

158+
def __init__(
159+
self,
160+
value: TuyaDPType | None = None,
161+
function: t.uint8_t = t.uint8_t(0),
162+
raw: bytes | None = None,
163+
):
164+
"""Convert from a zigpy typed value to a tuya data payload."""
165+
self.dp_type = None
166+
self.function = function
167+
self.raw = raw
168+
169+
if value is None:
170+
return
171+
elif isinstance(value, (t.bitmap8, t.bitmap16, t.bitmap32)):
172+
self.dp_type = TuyaDPType.BITMAP
173+
elif isinstance(value, (bool, t.Bool)):
174+
self.dp_type = TuyaDPType.BOOL
175+
elif isinstance(value, enum.Enum):
176+
self.dp_type = TuyaDPType.ENUM
177+
elif isinstance(value, int):
178+
self.dp_type = TuyaDPType.VALUE
179+
elif isinstance(value, str):
180+
self.dp_type = TuyaDPType.STRING
181+
else:
182+
self.dp_type = TuyaDPType.RAW
183+
184+
self.payload = value
185+
158186
@property
159187
def payload(
160188
self,
@@ -208,30 +236,28 @@ def payload(self, value):
208236
else:
209237
raise ValueError(f"Unknown {self.dp_type} datapoint type")
210238

211-
def __new__(cls, *args, **kwargs):
212-
"""Disable copy constructor."""
213-
return super().__new__(cls)
239+
def serialize(self) -> bytes:
240+
"""Serialize Tuya data."""
241+
return (
242+
self.dp_type.serialize()
243+
+ self.function.serialize()
244+
+ bytes([len(self.raw)])
245+
+ self.raw
246+
)
214247

215-
def __init__(self, value=None, function=0, *args, **kwargs):
216-
"""Convert from a zigpy typed value to a tuya data payload."""
217-
self.function = function
248+
@classmethod
249+
def deserialize(cls, data: bytes) -> tuple[TuyaData, bytes]:
250+
"""Deserialize Tuya data."""
251+
dp_type, data = TuyaDPType.deserialize(data)
252+
function, data = t.uint8_t.deserialize(data)
253+
raw, data = t.LVBytes.deserialize(data)
218254

219-
if value is None:
220-
return
221-
elif isinstance(value, (t.bitmap8, t.bitmap16, t.bitmap32)):
222-
self.dp_type = TuyaDPType.BITMAP
223-
elif isinstance(value, (bool, t.Bool)):
224-
self.dp_type = TuyaDPType.BOOL
225-
elif isinstance(value, enum.Enum): # type: ignore
226-
self.dp_type = TuyaDPType.ENUM
227-
elif isinstance(value, int):
228-
self.dp_type = TuyaDPType.VALUE
229-
elif isinstance(value, str):
230-
self.dp_type = TuyaDPType.STRING
231-
else:
232-
self.dp_type = TuyaDPType.RAW
255+
instance = cls()
256+
instance.dp_type = dp_type
257+
instance.function = function
258+
instance.raw = raw
233259

234-
self.payload = value
260+
return instance, data
235261

236262

237263
class Data(t.List, item_type=t.uint8_t):
@@ -1584,7 +1610,7 @@ def handle_cluster_request(
15841610
self.send_default_rsp(
15851611
hdr, status=foundation.Status.UNSUP_CLUSTER_COMMAND
15861612
)
1587-
return
1613+
return
15881614

15891615
try:
15901616
status = getattr(self, handler_name)(*args)

zhaquirks/xbee/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ class XBeeRemoteATRequest(LocalDataCluster):
284284
name=v[0].replace("%V", "PercentV").replace("V+", "VPlus"),
285285
schema={"param?": v[1]} if v[1] else {},
286286
is_manufacturer_specific=True,
287+
direction=foundation.Direction.Client_to_Server,
287288
)
288289
for k, v in zip(range(1, len(AT_COMMANDS) + 1), AT_COMMANDS.items())
289290
}
@@ -472,6 +473,7 @@ def handle_cluster_request(
472473
"value": Bytes,
473474
},
474475
is_manufacturer_specific=True,
476+
direction=foundation.Direction.Client_to_Server,
475477
)
476478
}
477479

@@ -525,6 +527,7 @@ def handle_cluster_request(
525527
name="io_sample",
526528
schema={"io_sample": IOSample},
527529
is_manufacturer_specific=True,
530+
direction=foundation.Direction.Client_to_Server,
528531
)
529532
}
530533

@@ -541,11 +544,15 @@ class XBeeEventRelayCluster(EventableCluster, LocalDataCluster, LevelControl):
541544
+ "_command_response",
542545
schema={"response?": v[1]} if v[1] else {},
543546
is_manufacturer_specific=True,
547+
direction=foundation.Direction.Client_to_Server,
544548
)
545549
for k, v in zip(range(1, len(AT_COMMANDS) + 1), AT_COMMANDS.items())
546550
}
547551
server_commands[SERIAL_DATA_CMD] = foundation.ZCLCommandDef(
548-
name="receive_data", schema={"data": str}, is_manufacturer_specific=True
552+
name="receive_data",
553+
schema={"data": str},
554+
is_manufacturer_specific=True,
555+
direction=foundation.Direction.Client_to_Server,
549556
)
550557

551558

@@ -605,13 +612,15 @@ def handle_cluster_request(
605612
name="send_data",
606613
schema={"data": BinaryString},
607614
is_manufacturer_specific=True,
615+
direction=foundation.Direction.Server_to_Client,
608616
)
609617
}
610618
server_commands = {
611619
SERIAL_DATA_CMD: foundation.ZCLCommandDef(
612620
name="receive_data",
613621
schema={"data": BinaryString},
614622
is_manufacturer_specific=True,
623+
direction=foundation.Direction.Client_to_Server,
615624
)
616625
}
617626

0 commit comments

Comments
 (0)