33from __future__ import annotations
44
55import asyncio
6+ import collections
67import itertools
78import logging
89import sys
2526)
2627from zigpy .zdo .types import SimpleDescriptor
2728
28- from zigpy_deconz .exception import APIException , CommandError
29+ from zigpy_deconz .exception import APIException , CommandError , MismatchedResponseError
2930import zigpy_deconz .types as t
3031import zigpy_deconz .uart
3132from zigpy_deconz .utils import restart_forever
@@ -415,7 +416,9 @@ class Deconz:
415416 def __init__ (self , app : Callable , device_config : dict [str , Any ]):
416417 """Init instance."""
417418 self ._app = app
418- self ._awaiting = {}
419+
420+ # [seq][cmd_id] = [fut1, fut2, ...]
421+ self ._awaiting = collections .defaultdict (lambda : collections .defaultdict (list ))
419422 self ._command_lock = asyncio .Lock ()
420423 self ._config = device_config
421424 self ._device_state = DeviceState (
@@ -459,7 +462,7 @@ async def connect(self) -> None:
459462
460463 await self .version ()
461464
462- device_state_rsp = await self ._command (CommandId .device_state )
465+ device_state_rsp = await self .send_command (CommandId .device_state )
463466 self ._device_state = device_state_rsp ["device_state" ]
464467
465468 self ._data_poller_task = asyncio .create_task (self ._data_poller ())
@@ -486,6 +489,13 @@ def close(self):
486489 self ._uart .close ()
487490 self ._uart = None
488491
492+ async def send_command (self , cmd , ** kwargs ) -> Any :
493+ while True :
494+ try :
495+ return await self ._command (cmd , ** kwargs )
496+ except MismatchedResponseError as exc :
497+ LOGGER .debug ("Firmware responded incorrectly (%s), retrying" , exc )
498+
489499 async def _command (self , cmd , ** kwargs ):
490500 payload = []
491501 tx_schema , _ = COMMAND_SCHEMAS [cmd ]
@@ -556,17 +566,16 @@ async def _command(self, cmd, **kwargs):
556566 self ._seq = (self ._seq % 255 ) + 1
557567
558568 fut = asyncio .Future ()
559- self ._awaiting [seq , cmd ] = fut
569+ self ._awaiting [seq ][ cmd ]. append ( fut )
560570
561571 try :
562572 async with asyncio_timeout (COMMAND_TIMEOUT ):
563573 return await fut
564574 except asyncio .TimeoutError :
565- LOGGER .warning (
566- "No response to '%s' command with seq id '0x%02x'" , cmd , seq
567- )
568- self ._awaiting .pop ((seq , cmd ), None )
575+ LOGGER .debug ("No response to '%s' command with seq %d" , cmd , seq )
569576 raise
577+ finally :
578+ self ._awaiting [seq ][cmd ].remove (fut )
570579
571580 def data_received (self , data : bytes ) -> None :
572581 command , _ = Command .deserialize (data )
@@ -577,7 +586,19 @@ def data_received(self, data: bytes) -> None:
577586
578587 _ , rx_schema = COMMAND_SCHEMAS [command .command_id ]
579588
580- fut = self ._awaiting .pop ((command .seq , command .command_id ), None )
589+ fut = None
590+ wrong_fut_cmd_id = None
591+
592+ try :
593+ fut = self ._awaiting [command .seq ][command .command_id ][0 ]
594+ except IndexError :
595+ # XXX: The firmware can sometimes respond with the wrong response. Find the
596+ # future associated with it so we can throw an appropriate error.
597+ for cmd_id , futs in self ._awaiting [command .seq ].items ():
598+ if futs :
599+ fut = futs [0 ]
600+ wrong_fut_cmd_id = cmd_id
601+ break
581602
582603 try :
583604 params , rest = t .deserialize_dict (command .payload , rx_schema )
@@ -614,7 +635,16 @@ def data_received(self, data: bytes) -> None:
614635
615636 exc = None
616637
617- if status != Status .SUCCESS :
638+ if wrong_fut_cmd_id is not None :
639+ exc = MismatchedResponseError (
640+ command .command_id ,
641+ params ,
642+ (
643+ f"Response is mismatched! Sent { wrong_fut_cmd_id } ,"
644+ f" received { command .command_id } "
645+ ),
646+ )
647+ elif status != Status .SUCCESS :
618648 exc = CommandError (status , f"{ command .command_id } , status: { status } " )
619649
620650 if fut is not None :
@@ -665,7 +695,9 @@ async def _data_poller(self):
665695 else :
666696 flags = t .DataIndicationFlags .Always_Use_NWK_Source_Addr
667697
668- rsp = await self ._command (CommandId .aps_data_indication , flags = flags )
698+ rsp = await self .send_command (
699+ CommandId .aps_data_indication , flags = flags
700+ )
669701 self ._handle_device_state_changed (
670702 status = rsp ["status" ], device_state = rsp ["device_state" ]
671703 )
@@ -687,7 +719,7 @@ async def _data_poller(self):
687719
688720 # Poll data confirm
689721 if DeviceStateFlags .APSDE_DATA_CONFIRM in self ._device_state .device_state :
690- rsp = await self ._command (CommandId .aps_data_confirm )
722+ rsp = await self .send_command (CommandId .aps_data_confirm )
691723
692724 self ._app .handle_tx_confirm (rsp ["request_id" ], rsp ["confirm_status" ])
693725 self ._handle_device_state_changed (
@@ -738,7 +770,7 @@ async def version(self):
738770 NetworkParameter .protocol_version
739771 )
740772
741- version_rsp = await self ._command (CommandId .version , reserved = 0 )
773+ version_rsp = await self .send_command (CommandId .version , reserved = 0 )
742774 self ._firmware_version = version_rsp ["version" ]
743775
744776 return self .firmware_version
@@ -753,7 +785,7 @@ async def read_parameter(
753785 else :
754786 value = read_param_type (parameter ).serialize ()
755787
756- rsp = await self ._command (
788+ rsp = await self .send_command (
757789 CommandId .read_parameter ,
758790 parameter_id = parameter_id ,
759791 parameter = value ,
@@ -770,7 +802,7 @@ async def write_parameter(
770802 self , parameter_id : NetworkParameter , parameter : Any
771803 ) -> None :
772804 read_param_type , write_param_type = NETWORK_PARAMETER_TYPES [parameter_id ]
773- await self ._command (
805+ await self .send_command (
774806 CommandId .write_parameter ,
775807 parameter_id = parameter_id ,
776808 parameter = write_param_type (parameter ).serialize (),
@@ -803,7 +835,7 @@ async def aps_data_request(
803835 await self ._free_slots_available_event .wait ()
804836
805837 try :
806- rsp = await self ._command (
838+ rsp = await self .send_command (
807839 CommandId .aps_data_request ,
808840 request_id = req_id ,
809841 flags = flags ,
@@ -830,17 +862,17 @@ async def aps_data_request(
830862 return
831863
832864 async def get_device_state (self ) -> DeviceState :
833- rsp = await self ._command (CommandId .device_state )
865+ rsp = await self .send_command (CommandId .device_state )
834866
835867 return rsp ["device_state" ]
836868
837869 async def change_network_state (self , new_state : NetworkState ) -> None :
838- await self ._command (CommandId .change_network_state , network_state = new_state )
870+ await self .send_command (CommandId .change_network_state , network_state = new_state )
839871
840872 async def add_neighbour (
841873 self , nwk : t .NWK , ieee : t .EUI64 , mac_capability_flags : t .uint8_t
842874 ) -> None :
843- await self ._command (
875+ await self .send_command (
844876 CommandId .update_neighbor ,
845877 action = UpdateNeighborAction .ADD ,
846878 nwk = nwk ,
0 commit comments