Skip to content

Commit fdebf06

Browse files
authored
Add fallback scanner for Matter nodes (#603)
1 parent 74b0ca3 commit fdebf06

File tree

1 file changed

+38
-5
lines changed

1 file changed

+38
-5
lines changed

matter_server/server/device_controller.py

+38-5
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
NODE_PING_TIMEOUT = 10
7676
NODE_PING_TIMEOUT_BATTERY_POWERED = 60
7777
NODE_MDNS_BACKOFF = 60
78+
FALLBACK_NODE_SCANNER_INTERVAL = 1800
7879

7980
MDNS_TYPE_OPERATIONAL_NODE = "_matter._tcp.local."
8081
MDNS_TYPE_COMMISSIONABLE_NODE = "_matterc._udp.local."
@@ -115,6 +116,7 @@ def __init__(
115116
self._node_lock: dict[int, asyncio.Lock] = {}
116117
self._aiobrowser: AsyncServiceBrowser | None = None
117118
self._aiozc: AsyncZeroconf | None = None
119+
self._fallback_node_scanner_timer: asyncio.TimerHandle | None = None
118120
self._sdk_executor = ThreadPoolExecutor(
119121
max_workers=1, thread_name_prefix="SDKExecutor"
120122
)
@@ -179,20 +181,24 @@ async def start(self) -> None:
179181
services,
180182
handlers=[self._on_mdns_service_state_change],
181183
)
184+
# set-up fallback node scanner
185+
asyncio.create_task(self._fallback_node_scanner())
182186

183187
async def stop(self) -> None:
184188
"""Handle logic on server stop."""
185189
if self.chip_controller is None:
186190
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
192192
if self._aiobrowser:
193193
await self._aiobrowser.async_cancel()
194+
if self._fallback_node_scanner_timer:
195+
self._fallback_node_scanner_timer.cancel()
194196
if self._aiozc:
195197
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 = {}
196202
# shutdown the sdk device controller
197203
await self._call_sdk(self.chip_controller.Shutdown)
198204
LOGGER.debug("Stopped.")
@@ -1275,3 +1281,30 @@ async def _node_offline(self, node_id: int) -> None:
12751281
node.available = False
12761282
self.server.signal_event(EventType.NODE_UPDATED, node)
12771283
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

Comments
 (0)