|
16 | 16 |
|
17 | 17 | from zigpy.types import ExtendedPanId, deserialize as list_deserialize
|
18 | 18 | from zigpy.zcl.clusters.security import IasZone
|
| 19 | +from zigpy.exceptions import DeliveryError |
19 | 20 |
|
20 | 21 | import zigpy_znp.config as conf
|
21 | 22 | import zigpy_znp.types as t
|
22 | 23 | import zigpy_znp.commands as c
|
| 24 | +from zigpy_znp.exceptions import InvalidCommandResponse |
23 | 25 |
|
24 | 26 | from zigpy_znp.api import ZNP
|
25 | 27 | from zigpy_znp.types.nvids import NwkNvIds
|
|
33 | 35 |
|
34 | 36 | ZDO_CONVERTERS = {
|
35 | 37 | ZDOCmd.Node_Desc_req: (
|
36 |
| - ZDOCmd.Node_Desc_rsp, |
37 | 38 | (
|
38 |
| - lambda addr, NWKAddrOfInterest: c.ZDOCommands.NodeDescReq.Req( |
| 39 | + lambda addr, device, NWKAddrOfInterest: c.ZDOCommands.NodeDescReq.Req( |
39 | 40 | DstAddr=addr, NWKAddrOfInterest=NWKAddrOfInterest
|
40 | 41 | )
|
41 | 42 | ),
|
42 |
| - ( |
43 |
| - lambda addr: c.ZDOCommands.NodeDescRsp.Callback( |
44 |
| - partial=True, Src=addr, Status=t.ZDOStatus.SUCCESS |
45 |
| - ) |
46 |
| - ), |
47 |
| - (lambda rsp, dev: [rsp.NodeDescriptor]), |
| 43 | + (lambda addr: c.ZDOCommands.NodeDescRsp.Callback(partial=True, Src=addr)), |
| 44 | + (lambda rsp: (ZDOCmd.Node_Desc_rsp, [rsp.NodeDescriptor])), |
48 | 45 | ),
|
49 | 46 | ZDOCmd.Active_EP_req: (
|
50 |
| - ZDOCmd.Active_EP_rsp, |
51 | 47 | (
|
52 |
| - lambda addr, NWKAddrOfInterest: c.ZDOCommands.ActiveEpReq.Req( |
| 48 | + lambda addr, device, NWKAddrOfInterest: c.ZDOCommands.ActiveEpReq.Req( |
53 | 49 | DstAddr=addr, NWKAddrOfInterest=NWKAddrOfInterest
|
54 | 50 | )
|
55 | 51 | ),
|
56 |
| - ( |
57 |
| - lambda addr: c.ZDOCommands.ActiveEpRsp.Callback( |
58 |
| - partial=True, Src=addr, Status=t.ZDOStatus.SUCCESS |
59 |
| - ) |
60 |
| - ), |
61 |
| - (lambda rsp, dev: [rsp.ActiveEndpoints]), |
| 52 | + (lambda addr: c.ZDOCommands.ActiveEpRsp.Callback(partial=True, Src=addr)), |
| 53 | + (lambda rsp: (ZDOCmd.Active_EP_rsp, [rsp.ActiveEndpoints])), |
62 | 54 | ),
|
63 | 55 | ZDOCmd.Simple_Desc_req: (
|
64 |
| - ZDOCmd.Simple_Desc_rsp, |
65 | 56 | (
|
66 |
| - lambda addr, NWKAddrOfInterest, EndPoint: c.ZDOCommands.SimpleDescReq.Req( |
| 57 | + # fmt: off |
| 58 | + lambda addr, device, NWKAddrOfInterest, EndPoint: \ |
| 59 | + c.ZDOCommands.SimpleDescReq.Req( |
67 | 60 | DstAddr=addr, NWKAddrOfInterest=NWKAddrOfInterest, Endpoint=EndPoint
|
68 | 61 | )
|
| 62 | + # fmt: on |
69 | 63 | ),
|
| 64 | + (lambda addr: c.ZDOCommands.SimpleDescRsp.Callback(partial=True, Src=addr)), |
| 65 | + (lambda rsp: (ZDOCmd.Simple_Desc_rsp, [rsp.SimpleDescriptor])), |
| 66 | + ), |
| 67 | + ZDOCmd.Mgmt_Leave_req: ( |
70 | 68 | (
|
71 |
| - lambda addr: c.ZDOCommands.SimpleDescRsp.Callback( |
72 |
| - partial=True, Src=addr, Status=t.ZDOStatus.SUCCESS |
| 69 | + lambda addr, device, DeviceAddress, Options: c.ZDOCommands.MgmtLeaveReq.Req( |
| 70 | + DstAddr=addr, |
| 71 | + IEEE=device.ieee, |
| 72 | + RemoveChildren_Rejoin=c.zdo.LeaveOptions(Options), |
73 | 73 | )
|
74 | 74 | ),
|
75 |
| - (lambda rsp, dev: [rsp.SimpleDescriptor]), |
| 75 | + (lambda addr: c.ZDOCommands.MgmtLeaveRsp.Callback(partial=True, Src=addr)), |
| 76 | + (lambda rsp: (ZDOCmd.Mgmt_Leave_rsp, [rsp.Status])), |
76 | 77 | ),
|
77 | 78 | }
|
78 | 79 |
|
@@ -480,34 +481,38 @@ async def _send_zdo_request(
|
480 | 481 | zdo_args, _ = list_deserialize(data, field_types)
|
481 | 482 | zdo_kwargs = dict(zip(field_names, zdo_args))
|
482 | 483 |
|
| 484 | + device = self.get_device(nwk=dst_addr.address) |
| 485 | + |
483 | 486 | # Call the converter with the ZDO request's kwargs
|
484 |
| - rsp_cluster, req_factory, callback_factory, converter = ZDO_CONVERTERS[cluster] |
485 |
| - request = req_factory(dst_addr.address, **zdo_kwargs) |
486 |
| - callback = callback_factory(dst_addr.address) |
| 487 | + req_factory, rsp_factory, zdo_rsp_factory = ZDO_CONVERTERS[cluster] |
| 488 | + request = req_factory(dst_addr.address, device, **zdo_kwargs) |
| 489 | + callback = rsp_factory(dst_addr.address) |
487 | 490 |
|
488 | 491 | LOGGER.debug(
|
489 |
| - "Intercepted AP ZDO request and replaced with %s - %s", request, callback |
| 492 | + "Intercepted AP ZDO request and replaced with %s/%s", request, callback |
490 | 493 | )
|
491 | 494 |
|
492 |
| - async with async_timeout.timeout(ZDO_REQUEST_TIMEOUT): |
493 |
| - response = await self._znp.request_callback_rsp( |
494 |
| - request=request, RspStatus=t.Status.Success, callback=callback |
495 |
| - ) |
| 495 | + try: |
| 496 | + async with async_timeout.timeout(ZDO_REQUEST_TIMEOUT): |
| 497 | + response = await self._znp.request_callback_rsp( |
| 498 | + request=request, RspStatus=t.Status.Success, callback=callback |
| 499 | + ) |
| 500 | + except InvalidCommandResponse as e: |
| 501 | + raise DeliveryError(f"Could not send command: {e.response.Status}") from e |
496 | 502 |
|
497 |
| - device = self.get_device(nwk=dst_addr.address) |
| 503 | + zdo_rsp_cluster, zdo_response_args = zdo_rsp_factory(response) |
498 | 504 |
|
499 | 505 | # Build up a ZDO response
|
500 | 506 | message = t.serialize_list(
|
501 |
| - [t.uint8_t(sequence), response.Status, response.NWK] |
502 |
| - + converter(response, device) |
| 507 | + [t.uint8_t(sequence), response.Status, response.NWK] + zdo_response_args |
503 | 508 | )
|
504 | 509 | LOGGER.trace("Pretending we received a ZDO message: %s", message)
|
505 | 510 |
|
506 | 511 | # We do not get any LQI info here
|
507 | 512 | self.handle_message(
|
508 | 513 | sender=device,
|
509 | 514 | profile=zigpy.profiles.zha.PROFILE_ID,
|
510 |
| - cluster=rsp_cluster, |
| 515 | + cluster=zdo_rsp_cluster, |
511 | 516 | src_ep=dst_ep,
|
512 | 517 | dst_ep=src_ep,
|
513 | 518 | message=message,
|
@@ -631,13 +636,22 @@ async def force_remove(self, device) -> None:
|
631 | 636 | """Forcibly remove device from NCP."""
|
632 | 637 | await self._znp.request(
|
633 | 638 | c.ZDOCommands.MgmtLeaveReq.Req(
|
634 |
| - DstAddr=device.nwk, |
| 639 | + DstAddr=0x0000, # We handle it |
635 | 640 | IEEE=device.ieee,
|
636 |
| - LeaveOptions=c.zdo.LeaveOptions.NONE, |
| 641 | + RemoveChildren_Rejoin=c.zdo.LeaveOptions.NONE, |
637 | 642 | ),
|
638 | 643 | RspStatus=t.Status.Success,
|
639 | 644 | )
|
640 | 645 |
|
| 646 | + # TODO: see what happens when we forcibly remove a device that isn't our child |
| 647 | + |
| 648 | + # Just wait for the response, removing the device will be handled upstream |
| 649 | + await self._znp.wait_for_response( |
| 650 | + c.ZDOCommands.LeaveInd.Callback( |
| 651 | + NWK=device.nwk, IEEE=device.ieee, partial=True |
| 652 | + ) |
| 653 | + ) |
| 654 | + |
641 | 655 | async def permit_ncp(self, time_s: int) -> None:
|
642 | 656 | response = await self._znp.request_callback_rsp(
|
643 | 657 | request=c.ZDOCommands.MgmtPermitJoinReq.Req(
|
|
0 commit comments