23
23
from chip .clusters .ClusterObjects import ALL_ATTRIBUTES , ALL_CLUSTERS , Cluster
24
24
from chip .discovery import DiscoveryType
25
25
from chip .exceptions import ChipStackError
26
+ from chip .native import PyChipError
26
27
from zeroconf import BadTypeInNameException , IPVersion , ServiceStateChange , Zeroconf
27
28
from zeroconf .asyncio import AsyncServiceBrowser , AsyncServiceInfo , AsyncZeroconf
28
29
80
81
NODE_SUBSCRIPTION_CEILING_WIFI = 60
81
82
NODE_SUBSCRIPTION_CEILING_THREAD = 60
82
83
NODE_SUBSCRIPTION_CEILING_BATTERY_POWERED = 600
83
- NODE_RESUBSCRIBE_ATTEMPTS_UNAVAILABLE = 3
84
- NODE_RESUBSCRIBE_TIMEOUT_OFFLINE = 30 * 60 * 1000
84
+ NODE_RESUBSCRIBE_ATTEMPTS_UNAVAILABLE = 2
85
+ NODE_RESUBSCRIBE_TIMEOUT_OFFLINE = 30 * 60
85
86
NODE_RESUBSCRIBE_FORCE_TIMEOUT = 5
86
87
NODE_PING_TIMEOUT = 10
87
88
NODE_PING_TIMEOUT_BATTERY_POWERED = 60
@@ -149,7 +150,8 @@ def __init__(
149
150
self ._node_last_seen_on_mdns : dict [int , float ] = {}
150
151
self ._nodes : dict [int , MatterNodeData ] = {}
151
152
self ._last_known_ip_addresses : dict [int , list [str ]] = {}
152
- self ._last_subscription_attempt : dict [int , int ] = {}
153
+ self ._resubscription_attempt : dict [int , int ] = {}
154
+ self ._first_resubscribe_attempt : dict [int , float ] = {}
153
155
self ._known_commissioning_params : dict [int , CommissioningParameters ] = {}
154
156
self ._known_commissioning_params_timers : dict [int , asyncio .TimerHandle ] = {}
155
157
self ._aiobrowser : AsyncServiceBrowser | None = None
@@ -1188,29 +1190,37 @@ def resubscription_attempted(
1188
1190
nextResubscribeIntervalMsec : int ,
1189
1191
) -> None :
1190
1192
# pylint: disable=unused-argument, invalid-name
1193
+ resubscription_attempt = self ._resubscription_attempt [node_id ]
1191
1194
node_logger .info (
1192
- "Previous subscription failed with Error: %s, re-subscribing in %s ms... " ,
1193
- terminationError ,
1194
- nextResubscribeIntervalMsec ,
1195
+ "Subscription failed with %s, resubscription attempt %s" ,
1196
+ str ( PyChipError ( code = terminationError )) ,
1197
+ resubscription_attempt ,
1195
1198
)
1196
- resubscription_attempt = self ._last_subscription_attempt [node_id ] + 1
1197
- self ._last_subscription_attempt [node_id ] = resubscription_attempt
1199
+ self ._resubscription_attempt [node_id ] = resubscription_attempt + 1
1200
+ if resubscription_attempt == 0 :
1201
+ self ._first_resubscribe_attempt [node_id ] = time .time ()
1198
1202
# Mark node as unavailable and signal consumers.
1199
1203
# We debounce it a bit so we only mark the node unavailable
1200
- # after some resubscription attempts and we shutdown the subscription
1201
- # if the resubscription interval exceeds 30 minutes (TTL of mdns).
1202
- # The node will be auto picked up by mdns if it's alive again.
1204
+ # after some resubscription attempts.
1203
1205
if resubscription_attempt >= NODE_RESUBSCRIBE_ATTEMPTS_UNAVAILABLE :
1204
1206
self ._node_unavailable (node_id )
1205
- if nextResubscribeIntervalMsec > NODE_RESUBSCRIBE_TIMEOUT_OFFLINE :
1207
+ # Shutdown the subscription if we tried to resubscribe for more than 30
1208
+ # minutes (typical TTL of mDNS). We assume this device got powered off.
1209
+ # When the device gets powered on again, it typically announces itself via
1210
+ # mDNS again. The mDNS browsing code will setup the subscription again.
1211
+ if (
1212
+ time .time () - self ._first_resubscribe_attempt [node_id ]
1213
+ > NODE_RESUBSCRIBE_TIMEOUT_OFFLINE
1214
+ ):
1206
1215
asyncio .create_task (self ._node_offline (node_id ))
1207
1216
1208
1217
def resubscription_succeeded (
1209
1218
transaction : Attribute .SubscriptionTransaction ,
1210
1219
) -> None :
1211
1220
# pylint: disable=unused-argument, invalid-name
1212
1221
node_logger .info ("Re-Subscription succeeded" )
1213
- self ._last_subscription_attempt [node_id ] = 0
1222
+ self ._resubscription_attempt [node_id ] = 0
1223
+ self ._first_resubscribe_attempt .pop (node_id , None )
1214
1224
# mark node as available and signal consumers
1215
1225
node = self ._nodes [node_id ]
1216
1226
if not node .available :
@@ -1233,7 +1243,7 @@ def resubscription_succeeded(
1233
1243
interval_ceiling = NODE_SUBSCRIPTION_CEILING_BATTERY_POWERED
1234
1244
else :
1235
1245
interval_ceiling = NODE_SUBSCRIPTION_CEILING_THREAD
1236
- self ._last_subscription_attempt [node_id ] = 0
1246
+ self ._resubscription_attempt [node_id ] = 0
1237
1247
# set-up the actual subscription
1238
1248
sub : Attribute .SubscriptionTransaction = (
1239
1249
await self ._chip_device_controller .read_attribute (
@@ -1606,15 +1616,12 @@ def _node_unavailable(
1606
1616
async def _node_offline (self , node_id : int ) -> None :
1607
1617
"""Mark node as offline."""
1608
1618
# shutdown existing subscriptions
1619
+ node_logger = self .get_node_logger (LOGGER , node_id )
1620
+ node_logger .info ("Node considered offline, shutdown subscription" )
1609
1621
await self ._chip_device_controller .shutdown_subscription (node_id )
1622
+
1610
1623
# mark node as unavailable (if it wasn't already)
1611
- node = self ._nodes [node_id ]
1612
- if not node .available :
1613
- return # nothing to do to
1614
- node .available = False
1615
- self .server .signal_event (EventType .NODE_UPDATED , node )
1616
- node_logger = self .get_node_logger (LOGGER , node_id )
1617
- node_logger .info ("Marked node as offline" )
1624
+ self ._node_unavailable (node_id )
1618
1625
1619
1626
async def _fallback_node_scanner (self ) -> None :
1620
1627
"""Scan for operational nodes in the background that are missed by mdns."""
0 commit comments