Context for developing and debugging Hubitat Elevation apps, drivers, and hub environment — sandbox constraints, lifecycle idioms, capability contracts, plus grounded deploy/log-tail/lint mechanisms.
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Updating device firmware is one of the few genuinely dangerous operations on a hub: a bad flash is closer to a brick than a git revert, and — the trap that surprised us — a flash that merely stalls can take the whole radio down with it. Flash deliberately, one radio at a time, guarded.
/hub/zwaveInfo) flashes OTA over the radio the device already uses. It handles Long Range + S2; the community driver-swap updaters (swap to a firmware-updater driver, call updateFirmware(url)) stall on LR/S2 ("Please wake up your sleepy device" on a mains device = wrong tool, not "wait longer").POST /hub/fileManager/upload/firmware (multipart), GET /hub/zwave/deviceFirmware/files, GET …/details?nodeId=N, POST …/start {nodeId,target:0,fileName}, GET …/progress?nodeId=N → {progress:{percent,stage}} (PROCESS→SENDING→DONE). It flashes in place — no driver change, the device keeps its real driver. skills/_scripts/hub_fw_update.py drives it; skills/firmware-update/SKILL.md is the procedure.zwaveJS:true in /hub/zwaveDetails/json). This skill/API is the zwaveJS updater./start, but do not rely on that.--wait-pid) or run different hubs in parallel.GET /hub/zwave/deviceFirmware/available?nodeId=N (the Z-Wave JS firmware service) lags and mis-matches: it offered ZEN04 2.30 when the vendor shipped 2.60 (missing the 2.40 redundant-report fix and the 2.50 SDK 7.19→7.24 S2/SPAN fix), and it offered a Springs shade a bogus downgrade to "1.5". Use it only as a rough "is there anything", never as the version/file.getzooz.com/firmware/<MODEL>_V<MM>R<mm>.zip (.gbl 700/800, .otz 500); Leviton — free .ota on leviton.com/content/dam/leviton/support/ (never cross model files); Ultraloq — free .gbl on file.u-tec.com. Some vendors publish no downloadable firmware (Springs/Somfy shades refuse "old motor" updates and aren't in the service) — then there is nothing to do; say so.Grounded live twice (2026-07-22). A flash that fails mid-transfer, or stalls (percent freezes and never emits DONE/FAILED), hangs the entire zwaveJS controller — not just that node:
success:true to every command but transmits nothing; every Z-Wave node freezes at once.active/reachable — invisible to the peer table (same class as zwave-zigbee-mesh.md's "peer looks fine but drops commands"). Even a local command on the owning hub returns success but never reaches the node (its lastTime doesn't advance).Why a big fleet hides it and a small one doesn't: on a hub with many devices to flash, successes after a failure keep re-kicking the radio so a transient hang self-clears; on a hub with few devices, or when the last/only flash fails, nothing follows to shake it loose and the radio stays wedged silently. The trailing/only failure is the dangerous one.
percent stops advancing for a few minutes at ANY level, not only at 0%. A frozen transfer never emits DONE/FAILED, so a plain start→wait-for-DONE loop hangs forever and takes the radio with it. (A canary-only guard missed a mid-transfer stall — that's why this is separate.)lastTime (it transmits nothing); a healthy hub always has some node reporting, and a rebooting hub advances lastTimes as it re-interviews. So nudge a known-healthy mains node and, over a wide window, check whether ANY node's lastTime advances — not whether one specific node answers inside a tight window. If nothing advances, the controller is hung → reboot and re-check; abort if it stays hung. Two false-positive traps this avoids, both learned live on a 75-LR-node hub: (a) do not probe after a successful flash — the success already proved the radio transmits, and the flashed device's own re-interview busies the radio → needless reboot; (b) a single-node/30 s check false-reads "hung" (and post-reboot "still hung," while zwaveJS is still interviewing) even though the radio is fine — checking any node over a wide window fixes both directions.lwrRssi is the last hop into the hub (rules/zwave-zigbee-mesh.md).
hops == 0) and carries a readable lwrRssi.lwrRssi.Never fire-and-forget across marginal devices. Flash devices whose own link is measured and strong. Leave floor-RSSI and unmeasured-link ones on their current firmware.
GET /hub/advanced/getManagementToken → GET /management/reboot?token=<token> (~2–3 min; zwaveJS re-interviews every node; confirm a fresh systemStart in /hub/eventsJson). On the automation hub a reboot is a ~3-min blackout, so prevention (the guardrails) beats recovery here.nodeState OK; a stalled node sometimes even completes to target once the reboot frees the queue.device.data.firmwareVersion until the post-reboot re-interview, so poll /hub/zwave/deviceFirmware/details?nodeId=N → targets[0].version until it flips. Report per device — updated / already-current / skipped-weak / skipped-unknown / failed — and name any failure with its current version (on old firmware, not bricked, retryable).