|
75 | 75 | NODE_PING_TIMEOUT = 10
|
76 | 76 | NODE_PING_TIMEOUT_BATTERY_POWERED = 60
|
77 | 77 | NODE_MDNS_BACKOFF = 60
|
| 78 | +FALLBACK_NODE_SCANNER_INTERVAL = 1800 |
78 | 79 |
|
79 | 80 | MDNS_TYPE_OPERATIONAL_NODE = "_matter._tcp.local."
|
80 | 81 | MDNS_TYPE_COMMISSIONABLE_NODE = "_matterc._udp.local."
|
@@ -115,6 +116,7 @@ def __init__(
|
115 | 116 | self._node_lock: dict[int, asyncio.Lock] = {}
|
116 | 117 | self._aiobrowser: AsyncServiceBrowser | None = None
|
117 | 118 | self._aiozc: AsyncZeroconf | None = None
|
| 119 | + self._fallback_node_scanner_timer: asyncio.TimerHandle | None = None |
118 | 120 | self._sdk_executor = ThreadPoolExecutor(
|
119 | 121 | max_workers=1, thread_name_prefix="SDKExecutor"
|
120 | 122 | )
|
@@ -179,20 +181,24 @@ async def start(self) -> None:
|
179 | 181 | services,
|
180 | 182 | handlers=[self._on_mdns_service_state_change],
|
181 | 183 | )
|
| 184 | + # set-up fallback node scanner |
| 185 | + asyncio.create_task(self._fallback_node_scanner()) |
182 | 186 |
|
183 | 187 | async def stop(self) -> None:
|
184 | 188 | """Handle logic on server stop."""
|
185 | 189 | if self.chip_controller is None:
|
186 | 190 | raise RuntimeError("Device Controller not initialized.")
|
187 |
| - # unsubscribe all node subscriptions |
188 |
| - for sub in self._subscriptions.values(): |
189 |
| - await self._call_sdk(sub.Shutdown) |
190 |
| - self._subscriptions = {} |
191 |
| - # shutdown (and cleanup) mdns browser |
| 191 | + # shutdown (and cleanup) mdns browser and fallback node scanner |
192 | 192 | if self._aiobrowser:
|
193 | 193 | await self._aiobrowser.async_cancel()
|
| 194 | + if self._fallback_node_scanner_timer: |
| 195 | + self._fallback_node_scanner_timer.cancel() |
194 | 196 | if self._aiozc:
|
195 | 197 | await self._aiozc.async_close()
|
| 198 | + # unsubscribe all node subscriptions |
| 199 | + for sub in self._subscriptions.values(): |
| 200 | + await self._call_sdk(sub.Shutdown) |
| 201 | + self._subscriptions = {} |
196 | 202 | # shutdown the sdk device controller
|
197 | 203 | await self._call_sdk(self.chip_controller.Shutdown)
|
198 | 204 | LOGGER.debug("Stopped.")
|
@@ -1275,3 +1281,30 @@ async def _node_offline(self, node_id: int) -> None:
|
1275 | 1281 | node.available = False
|
1276 | 1282 | self.server.signal_event(EventType.NODE_UPDATED, node)
|
1277 | 1283 | LOGGER.info("Marked node %s as offline", node_id)
|
| 1284 | + |
| 1285 | + async def _fallback_node_scanner(self) -> None: |
| 1286 | + """Scan for operational nodes in the background that are missed by mdns.""" |
| 1287 | + # This code could/should be removed in the future and is added to have a fallback |
| 1288 | + # to discover operational nodes that got somehow missed by zeroconf. |
| 1289 | + # the issue in zeroconf is being investigated and in the meanwhile we have this fallback. |
| 1290 | + for node_id, node in self._nodes.items(): |
| 1291 | + if node.available: |
| 1292 | + continue |
| 1293 | + now = time.time() |
| 1294 | + last_seen = self._node_last_seen.get(node_id, 0) |
| 1295 | + if now - last_seen < FALLBACK_NODE_SCANNER_INTERVAL: |
| 1296 | + continue |
| 1297 | + if await self.ping_node(node_id, attempts=3): |
| 1298 | + LOGGER.info("Node %s discovered using fallback ping", node_id) |
| 1299 | + await self._setup_node(node_id) |
| 1300 | + |
| 1301 | + def reschedule_self() -> None: |
| 1302 | + self._fallback_node_scanner_timer = None |
| 1303 | + asyncio.create_task(self._fallback_node_scanner()) |
| 1304 | + |
| 1305 | + # reschedule task to run at next interval |
| 1306 | + if TYPE_CHECKING: |
| 1307 | + assert self.server.loop |
| 1308 | + self._fallback_node_scanner_timer = self.server.loop.call_later( |
| 1309 | + FALLBACK_NODE_SCANNER_INTERVAL, reschedule_self |
| 1310 | + ) |
0 commit comments