68
68
LOGGER = logging .getLogger (__name__ )
69
69
70
70
71
+ class RetryMethod (t .enum_flag_uint8 ):
72
+ NONE = 0
73
+ AssocRemove = 2 << 0
74
+ RouteDiscovery = 2 << 1
75
+ LastGoodRoute = 2 << 2
76
+ IEEEAddress = 2 << 3
77
+
78
+
71
79
class ControllerApplication (zigpy .application .ControllerApplication ):
72
80
SCHEMA = conf .CONFIG_SCHEMA
73
81
SCHEMA_DEVICE = conf .SCHEMA_DEVICE
@@ -809,19 +817,19 @@ def _find_endpoint(self, dst_ep: int, profile: int, cluster: int) -> int:
809
817
810
818
async def _send_request_raw (
811
819
self ,
812
- dst_addr ,
813
- dst_ep ,
814
- src_ep ,
815
- profile ,
816
- cluster ,
817
- sequence ,
818
- options ,
819
- radius ,
820
- data ,
820
+ dst_addr : t . AddrModeAddress ,
821
+ dst_ep : int ,
822
+ src_ep : int ,
823
+ profile : int ,
824
+ cluster : int ,
825
+ sequence : int ,
826
+ options : c . af . TransmitOptions ,
827
+ radius : int ,
828
+ data : bytes ,
821
829
* ,
822
- relays = None ,
823
- extended_timeout = False ,
824
- ):
830
+ relays : list [ int ] | None = None ,
831
+ extended_timeout : bool = False ,
832
+ ) -> None :
825
833
"""
826
834
Used by `request`/`mrequest`/`broadcast` to send a request.
827
835
Picks the correct request sending mechanism and fixes endpoint information.
@@ -922,9 +930,7 @@ async def _send_request_raw(
922
930
923
931
if dst_ep == ZDO_ENDPOINT or dst_addr .mode == t .AddrMode .Broadcast :
924
932
# Broadcasts and ZDO requests will not receive a confirmation
925
- response = await self ._znp .request (
926
- request = request , RspStatus = t .Status .SUCCESS
927
- )
933
+ await self ._znp .request (request = request , RspStatus = t .Status .SUCCESS )
928
934
else :
929
935
async with async_timeout .timeout (
930
936
EXTENDED_DATA_CONFIRM_TIMEOUT
@@ -956,8 +962,6 @@ async def _send_request_raw(
956
962
response ,
957
963
)
958
964
959
- return response
960
-
961
965
@combine_concurrent_calls
962
966
async def _discover_route (self , nwk : t .NWK ) -> None :
963
967
"""
@@ -1006,18 +1010,15 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
1006
1010
1007
1011
dst_addr = t .AddrModeAddress .from_zigpy_type (packet .dst )
1008
1012
1009
- status = None
1010
- response = None
1013
+ succeeded = False
1011
1014
association = None
1012
1015
force_relays = None
1013
1016
1014
1017
if packet .source_route is not None :
1015
1018
force_relays = packet .source_route
1016
1019
1017
- tried_assoc_remove = False
1018
- tried_route_discovery = False
1019
- tried_last_good_route = False
1020
- tried_ieee_address = False
1020
+ retry_methods = RetryMethod .NONE
1021
+ last_retry_method = RetryMethod .NONE
1021
1022
1022
1023
# Don't release the concurrency-limiting semaphore until we are done trying.
1023
1024
# There is no point in allowing requests to take turns getting buffer errors.
@@ -1047,7 +1048,7 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
1047
1048
if route_status .Status != c .zdo .RoutingStatus .SUCCESS :
1048
1049
await self ._discover_route (dst_addr .address )
1049
1050
1050
- response = await self ._send_request_raw (
1051
+ await self ._send_request_raw (
1051
1052
dst_addr = dst_addr ,
1052
1053
dst_ep = packet .dst_ep ,
1053
1054
src_ep = packet .src_ep ,
@@ -1060,7 +1061,7 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
1060
1061
relays = force_relays ,
1061
1062
extended_timeout = packet .extended_timeout ,
1062
1063
)
1063
- status = response . Status
1064
+ succeeded = True
1064
1065
break
1065
1066
except InvalidCommandResponse as e :
1066
1067
status = e .response .Status
@@ -1078,23 +1079,27 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
1078
1079
or dst_addr .mode not in (t .AddrMode .NWK , t .AddrMode .IEEE )
1079
1080
):
1080
1081
LOGGER .debug (
1081
- "Request failed (%s), retry attempt %s of %s" ,
1082
+ "Request failed (%s), retry attempt %s of %s (%s) " ,
1082
1083
e ,
1083
1084
attempt + 1 ,
1084
1085
REQUEST_MAX_RETRIES ,
1086
+ retry_methods .name ,
1085
1087
)
1086
1088
await asyncio .sleep (3 * REQUEST_ERROR_RETRY_DELAY )
1087
1089
continue
1088
1090
1089
1091
# If we can't contact the device by forcing a specific route,
1090
- # there is not point in trying this more than once.
1091
- if tried_last_good_route and force_relays is not None :
1092
+ # there is no point in trying this more than once.
1093
+ if (
1094
+ retry_methods & RetryMethod .LastGoodRoute
1095
+ and force_relays is not None
1096
+ ):
1092
1097
force_relays = None
1093
1098
1094
1099
# If we fail to contact the device with its IEEE address, don't
1095
1100
# try again.
1096
1101
if (
1097
- tried_ieee_address
1102
+ retry_methods & RetryMethod . IEEEAddress
1098
1103
and dst_addr .mode == t .AddrMode .IEEE
1099
1104
and device is not None
1100
1105
):
@@ -1111,7 +1116,7 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
1111
1116
status == t .Status .MAC_TRANSACTION_EXPIRED
1112
1117
and device is not None
1113
1118
and association is None
1114
- and not tried_assoc_remove
1119
+ and not retry_methods & RetryMethod . AssocRemove
1115
1120
and self ._znp .version >= 3.30
1116
1121
):
1117
1122
association = await self ._znp .request (
@@ -1129,7 +1134,8 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
1129
1134
await self ._znp .request (
1130
1135
c .UTIL .AssocRemove .Req (IEEE = device .ieee )
1131
1136
)
1132
- tried_assoc_remove = True
1137
+ retry_methods |= RetryMethod .AssocRemove
1138
+ last_retry_method = RetryMethod .AssocRemove
1133
1139
1134
1140
# Route discovery must be performed right after
1135
1141
await self ._discover_route (device .nwk )
@@ -1138,39 +1144,46 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
1138
1144
"The UTIL.AssocRemove command is available only"
1139
1145
" in Z-Stack 3 releases built after 20201017"
1140
1146
)
1141
- elif not tried_last_good_route and device is not None :
1147
+ elif (
1148
+ not retry_methods & RetryMethod .LastGoodRoute
1149
+ and device is not None
1150
+ ):
1142
1151
# `ZDO.SrcRtgInd` callbacks tell us the last path taken by
1143
1152
# messages from the device back to the coordinator. Sending
1144
1153
# packets backwards via this same route may work.
1145
1154
force_relays = (device .relays or [])[::- 1 ]
1146
- tried_last_good_route = True
1155
+ retry_methods |= RetryMethod .LastGoodRoute
1156
+ last_retry_method = RetryMethod .LastGoodRoute
1147
1157
elif (
1148
- not tried_route_discovery
1158
+ not retry_methods & RetryMethod . RouteDiscovery
1149
1159
and dst_addr .mode == t .AddrMode .NWK
1150
1160
):
1151
1161
# If that doesn't work, try re-discovering the route.
1152
1162
# While we can in theory poll and wait until it is fixed,
1153
1163
# letting the retry mechanism deal with it simpler.
1154
1164
await self ._discover_route (dst_addr .address )
1155
- tried_route_discovery = True
1165
+ retry_methods |= RetryMethod .RouteDiscovery
1166
+ last_retry_method = RetryMethod .RouteDiscovery
1156
1167
elif (
1157
- not tried_ieee_address
1168
+ not retry_methods & RetryMethod . IEEEAddress
1158
1169
and device is not None
1159
1170
and dst_addr .mode == t .AddrMode .NWK
1160
1171
):
1161
1172
# Try using the device's IEEE address instead of its NWK.
1162
1173
# If it works, the NWK will be updated when relays arrive.
1163
- tried_ieee_address = True
1174
+ retry_methods |= RetryMethod .IEEEAddress
1175
+ last_retry_method = RetryMethod .IEEEAddress
1164
1176
dst_addr = t .AddrModeAddress (
1165
1177
mode = t .AddrMode .IEEE ,
1166
1178
address = device .ieee ,
1167
1179
)
1168
1180
1169
1181
LOGGER .debug (
1170
- "Request failed (%s), retry attempt %s of %s" ,
1182
+ "Request failed (%s), retry attempt %s of %s (%s) " ,
1171
1183
e ,
1172
1184
attempt + 1 ,
1173
1185
REQUEST_MAX_RETRIES ,
1186
+ retry_methods .name ,
1174
1187
)
1175
1188
1176
1189
# We've tried everything already so at this point just wait
@@ -1181,11 +1194,15 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
1181
1194
f" { status !r} " ,
1182
1195
status = status ,
1183
1196
)
1197
+
1198
+ self .state .counters [f"Retry_{ last_retry_method .name } " ][
1199
+ attempt
1200
+ ].increment ()
1184
1201
finally :
1185
1202
# We *must* re-add the device association if we previously removed it but
1186
1203
# the request still failed. Otherwise, it may be a direct child and we will
1187
1204
# not be able to find it again.
1188
- if tried_assoc_remove and response is None :
1205
+ if not succeeded and retry_methods & RetryMethod . AssocRemove :
1189
1206
await self ._znp .request (
1190
1207
c .UTIL .AssocAdd .Req (
1191
1208
NWK = association .Device .shortAddr ,
0 commit comments