Idea
Z-Wave is mesh-routed but unlike Zigbee/Thread, the routing is more graph-than-tree: each node has multiple neighbors and there's no single "parent". A pure ASCII tree would lose information.
Instead: render a per-node neighbor table on a dedicated Z-Wave page.
Example output:
## Z-Wave-Mesh
Hops to controller per node (lower = better):
| Node | Type | Hops | Neighbors | RSSI/LQI |
| --- | --- | --- | --- | --- |
| Aeotec Multisensor 6 (#7) | Sensor | 1 | #1 (controller), #4 | -65 dBm |
| Fibaro Dimmer (#4) | Switch | 0 (direct) | #1 (controller), #5, #7 | -55 dBm |
| Door Lock (#9) | Battery FLiRS | 2 | #4, #5 | -78 dBm |
| ... |
## Routing-Tabelle
| Source → Target | Route via | Status |
| --- | --- | --- |
| Controller → #9 (Door Lock) | #4 → #5 | OK |
| Controller → #7 | direct | OK |
| ... |
```
Useful for: spotting nodes far from controller, identifying which routers carry the most load, planning where to add a strategically-placed repeater.
Data sources
- Z-Wave JS integration exposes per-node
neighbors and routes_to_controller via diagnostics.
- HA service
zwave_js.refresh_node_info triggers fresh neighbor data.
- Per-node
device_class distinguishes Battery vs Mains-powered (matters for routing).
Files
custom_components/bookstack_sync/extractor.py — ZWaveNetworkSnapshot { nodes: list[ZWaveNode] }, each node has node_id, name, device_class, is_controller, neighbors: list[int], hops_to_controller, routes_to_controller: list[list[int]].
custom_components/bookstack_sync/renderer.py — render_zwave_auto_block(...) — two tables (per-node + routing), no tree.
_strings.py — title_zwave, bundle_zwave, table headers, zwave_role_*, zwave_route_status_* (DE+EN).
Edge cases
- No Z-Wave installed → skip
- Battery-powered FLiRS (Frequently Listening Receiver Slave) nodes are part of mesh but only briefly — note this in legend
- Node names sometimes not friendly (just "Node 7") → display node_id alongside name when name is generic
Acceptance
- Renders only when Z-Wave JS integration loaded
- Hops sorted ascending (controller-direct first)
- Routes table grouped by source
- Test: snapshot with controller + 3 nodes (one direct, two routed) → expected tables
Why no tree
Z-Wave is mesh — same node can be reached via multiple parents. A tree would force one path and hide alternatives. Tables make all routes visible. If the user really wants a tree-ish view, that can be a follow-up "primary route per node" rendered as ASCII.
Idea
Z-Wave is mesh-routed but unlike Zigbee/Thread, the routing is more graph-than-tree: each node has multiple neighbors and there's no single "parent". A pure ASCII tree would lose information.
Instead: render a per-node neighbor table on a dedicated Z-Wave page.
Example output:
Useful for: spotting nodes far from controller, identifying which routers carry the most load, planning where to add a strategically-placed repeater.
Data sources
neighborsandroutes_to_controllervia diagnostics.zwave_js.refresh_node_infotriggers fresh neighbor data.device_classdistinguishes Battery vs Mains-powered (matters for routing).Files
custom_components/bookstack_sync/extractor.py—ZWaveNetworkSnapshot { nodes: list[ZWaveNode] }, each node hasnode_id,name,device_class,is_controller,neighbors: list[int],hops_to_controller,routes_to_controller: list[list[int]].custom_components/bookstack_sync/renderer.py—render_zwave_auto_block(...)— two tables (per-node + routing), no tree._strings.py—title_zwave,bundle_zwave, table headers,zwave_role_*,zwave_route_status_*(DE+EN).Edge cases
Acceptance
Why no tree
Z-Wave is mesh — same node can be reached via multiple parents. A tree would force one path and hide alternatives. Tables make all routes visible. If the user really wants a tree-ish view, that can be a follow-up "primary route per node" rendered as ASCII.