Idea: expose `node.lastSeen` in the Zigbee SDK

Mains-powered Zigbee devices (smart plugs, wall switches, relays) do not send a ZDO End Device Announce when they go offline — they simply stop transmitting. Battery-powered sensors can also go silent without any indication. There is currently no way for an app to detect this reliably.

Real example: I recently found an Aqara temperature sensor that had been paired and visible in the Aqara app, but had not reported a single reading for 5 months. Homey had no way to flag it as unavailable. Some Aqara door sensors also required re-pairing for the same reason — they were silently dead.

The root cause: apps cannot read the lastSeen timestamp that the Homey Zigbee coordinator already tracks internally — it is even visible through the Homey Web API. The data exists; it is just not exposed.

The current workaround requires hooking into node.handleFrame to manually record activity, persist it via setStoreValue on every frame, and run a custom watchdog interval. I implemented this pattern in a personal app (com.gpm.homesuite) for switches and relays — but it is fragile, duplicates work the coordinator already does, and most apps simply don’t have it at all.

The fix is straightforward: expose a read-only node.lastSeen (Unix ms) on the object returned by homey.zigbee.getNode().

const node = await this.homey.zigbee.getNode(this);
if (Date.now() - node.lastSeen > TIMEOUT_MS) {
  await this.setUnavailable('No activity');
}

This would let any app implement availability tracking in a few lines, without touching internal APIs.

Full technical write-up on GitHub: Feature request: expose node.lastSeen timestamp in the ZigBee node object · Issue #180 · athombv/node-homey-zigbeedriver · GitHub

If you’ve hit this problem too, a :+1: on the issue helps prioritization.

As far as I can see in Developer Tools, Homey doesn’t keep track of a “last seen” date:

However, if you built the app yourself, you can just keep track of the last seen date inside your app, right? Just make your save the last reporting time to memory, and use that as a “last seen” timestamp.

Thanks for sharing your screenshot. I can see why it looked like the data wasn’t there — in your setup, Last Seen shows ?for all nodes.

In my Homey Pro 2023, the same Developer Tools column shows precise ISO timestamps for every node:

Beyond the UI, I can confirm the data is programmatically accessible via the Web API. This HomeyScript reads node.lastSeen from Homey.zigbee.getState() and uses it to detect silent end devices:

const zigbeeState = await Homey.zigbee.getState();
for (const node of Object.values(zigbeeState.nodes)) {
  const lastSeenTs = node.lastSeen ? new Date(node.lastSeen).getTime() : NaN;
  const age = Number.isNaN(lastSeenTs) ? Infinity : (Date.now() - lastSeenTs);
  if (age >= THRESHOLD_MS) { /* mark as unavailable */ }
}

A HomeyScript — running with homey:manager:api — can already do this today. An SDK app using homey.zigbee.getNode()cannot, without requesting that same elevated permission just to read a single timestamp. The data exists. The ask is simply to surface it in the SDK without requiring homey:manager:api.
This also affects users of third-party apps like the official Aqara integration. Those apps don’t implement availability watchdogs, and end users have no way to add them. The practical result: a device can go silent for months — I had an Aqara temperature sensor that stopped reporting in September 2025 and was never flagged as unavailable. Native node.lastSeen in the SDK would allow any app, including Aqara’s, to detect this automatically with minimal code.

To illustrate the real cost of the missing primitive: I maintain an AvailabilityManager library in my own app that implements availability tracking for Zigbee devices. The current implementation requires ~300 lines — most of which exist solely to work around the absence of node.lastSeen:

  • _installHandleFrameHook() (~40 lines) to intercept every inbound frame

  • throttled setStoreValue on every frame to persist the timestamp

  • cleanup logic to restore the original handler on uninstall

  • a guard flag (_availabilityHookInstalled) to handle shared node references on rejoin

With a native node.lastSeen, the watchdog would read the timestamp directly from the node — no hook, no store I/O, no cleanup. The implementation would drop to ~150 lines and the silent-breakage-on-rejoin risk would disappear entirely.

This is the concrete overhead every developer pays today for a value the coordinator already has.