RobotDx › Reports › IO diagnostics: the converged design. Published copy, redacted. Taken out: 1 home paths, 16 private links unwrapped, 30 serials, 15 screenshots with serials and machine names covered. Evidence for D01, D02, D03, D04, D08. Written for Zach at the time, so it says “you”.
Your Oct 22 feedback on round 1, set against ~50 tools that already do part of this, turned into one design. Read the verdict, then go as deep as you want.1,2
The design in five lines
A small top-bar chip (“IO 3 ⚠”) with a Recent list opens one window. It does not live in Ubuntu’s Quick Settings.1,2
The window is an entity tree plus detail: icon · status · name · id or count, search on top, parents take their worst child’s status; click a row for RealSense-Viewer-level detail.1
The same tree flips to a table (status, message, rate measured vs expected, last seen, firmware, errors), and an activity log runs underneath.1
Two sources build the tree: the OS gives the USB skeleton, drivers hang buses and nodes under their device, and every edge carries its own link health.1
Drivers own everything device-specific: they emit status records and register utility buttons; IO diagnostics renders and never speaks a device protocol.1
Read-only or command?
Both, as two planes
The status plane is read-only and always on. The action plane is how printers do it: the driver registers its utilities, the window shows the buttons, the driver runs them.1,5
Who may talk to the hardware?
Whoever owns the port
While the stack runs, IO diagnostics only listens. When nothing runs, it may briefly start the driver’s own check after a holder and bus-lock check. On Oct 21 a config read collided with an open tview, the exact case this prevents.1,2
What is the contract?
ROS DiagnosticStatus + 3 fields
path, expected_period_s and identity (Home Assistant device_info, whose via_device is the parent edge). No new schema, and no ROS needed to emit it.1,12,25
The converged mock, tree mode at the 18:11 plug with CAN 15 selected. Table, activity and the action dialog are in Converged mock below.
What you picked
Your own screenshots from the Oct 22 note, grouped by what each one added. Quotes are verbatim.2
V4’s window: a list on the left, detail on the right
“a nice simple layout... basically a bar on the left”
What it contributed: The window’s shape: a left column of devices, a search box on top, click a row for detail on the right. Round 1’s V4 dashboard became the frame for everything below.2
Clearpath “All Diagnostics”: a tree with icon, status and name
“You have a left side hand.. that is essentially a tree.”
What it contributed: The tree row (status icon · name · path underneath) and the detail drawer’s fields: Path, Hardware ID, Level, Message, Values. You called it “the rockwell dash board”: Clearpath has been part of Rockwell Automation since Oct 2023.2,14,31
V1’s rows: name, id, count and status in one line, but not in Quick Settings
“I like how we see the kinda top level name, and ID”
What it contributed: The row grammar (icon · name · id or count · status), and one firm “no”: not inside Ubuntu’s built-in menu (“I wouldn’t put it on the built in ubunut thing”). The entry point is a chip of its own.2
V3’s button placeholder, and “how do printers do it?”
“I think we can have place holders for buttons”
What it contributed: The action plane. A printer driver registers its utilities and the OS dialog only shows them; here each driver registers its buttons and executes them itself.2
V2’s chip and Recent list: an activity log
“interesting idea here with like a activity log”
What it contributed: The activity log (plugged, unplugged, re-enumerated, fault in and out), and the idea you liked most: the computer should be “monitoring the wires” itself.2
ros2_medkit: an Entity Tree, and Operations
“child parent relations you can do drop down”
What it contributed: Parent/child nesting for many nodes on one bus (“Ok they call it an Entiy Tree”), and the Operations tab: named actions with a Run button, which is the action plane already built.2,15,16
Greenwave Monitor: a table with measured vs expected frequency
“turn it into more of like a table view for fast high level overview”
What it contributed: Table mode: Clearpath’s nested first column with aligned columns to the right, which should carry more than Message, “stuff like Frequency”: rate measured vs expected, last seen, firmware, errors.2,17
rqt_robot_monitor: roll-up and a timeline strip
“Yes the roll up view I think important”
What it contributed: Worst-child-wins roll-up, with counts on the parent (“(E: 1, W: 0) Computers”), and the colored history strip that keeps a one-off red tick visible.2,33
RealSense Viewer: the level of ambition
“this is kinda at the level of like realsense viewer”
What it contributed: The scope line: plug it in, see what it is and how it is connected, a few “simple command controls”, and no robot control. Link speed sits beside the name (USB 3.2 vs USB 2.0).2,24
Ten examples worth stealing from
Picked from about 50 tools across three survey lanes: four ROS-native, four vendor tools, two general device managers3,4,5. Each card: the pattern, and exactly what IO diagnostics takes from it.
One page of cards: a Timeline of status dots with Pause, then Errors and Warnings cards that read “No Errors” with a green check when clean, then All Diagnostics.
All Diagnostics is one expandable tree (name above its path, Message column); selecting a row opens a details drawer with the key/values. “Generate Capture” zips a support bundle (admin only).
IO diagnostics takes: The tree row and drawer fields as they are, positive empty states, and the capture button as our “copy diagnostics for support” action.3,14
2. ros2_medkit web UIROS-native
github.com · Apache-2.0 · Linux (ROS 2 gateway; web UI in Docker, “early prototype”)
Left Entity Tree (server › area › component › app) with type pills, ⌘K search and a Logical/Functional toggle; breadcrumb across the top.
Each entity gets tabs with counts: Overview · Data 13 · Operations 7 · Config 35 · Faults. Its bridge maps STALE to CRITICAL: a device that stopped talking outranks one reporting an error.
IO diagnostics takes: The detail panel’s skeleton (values, actions, read-only config, faults, each with a count) and STALE treated as worse than ERROR for hardware.3,15
github.com · BSD-3 · Linux (needs a ROS 2 install)
The record: DiagnosticStatus = level (OK / WARN / ERROR / STALE) + name + message + hardware_id + string key/values, stable since ROS 1. The aggregator rolls it into a tree where the parent takes the worst child, and “expected” makes an absent device a warning, not silence.
rqt_robot_monitor stacks Error and Warn panes (leaf items only) above the All devices tree, with a history strip: “Last message received N seconds ago”, Pause.
IO diagnostics takes: The record shape unchanged, worst-child roll-up, “expected but absent”, and the history strip as the cheapest honest view of a flaky link.3,12,13
github.com · Foxglove: proprietary, sign-in · Lichtblick fork: MPL-2.0, Linux desktop / web / Docker
Summary feed on top (minimum-level dropdown, fuzzy “Filter hardware id”, sort by level, pin), Detail below: level-colored title and a zebra key/value table.
A per-panel stale timeout (5 s default) rewrites old entries to STALE on the client. Lichtblick accepts any channel whose schema is diagnostic_msgs/msg/DiagnosticArray, JSON included.
IO diagnostics takes: The zebra key/value table, hardware_id as the search key, and a free second view: emit the same records and Lichtblick renders them with zero UI code (unproven; a 30-minute spike).3,18,19
The first screen is “Devices (17)”, as cards or a table: Name | Model | ID | CAN Bus | FirmVer | Status. The border color is the firmware state and is always repeated as a sentence (“A new firmware version is available.”); a duplicate ID is its own red state.
A bulb on every card blinks the device; the footer carries “Latency: 0ms – Jitter: 0ms”.
IO diagnostics takes: Table-mode columns, color always repeated in words, duplicate-ID as a named state, identify on every row. Reference only: no Linux.4,21
Passive discovery: every node broadcasts a heartbeat, so it appears on its own. Table: Node ID | Name | Mode | Health | Uptime | vendor code.
Double-click opens the node: software version + CRC, hardware version + unique ID, then Restart and Update firmware right under the identity, then config (Fetch / Store / Erase).
IO diagnostics takes: Presence from a declared heartbeat (our expected_period_s), uptime that resets exposes brown-outs, and identity + actions in one detail panel.4,22
7. REV Hardware Clientvendor
docs.revrobotics.com · Free (proprietary) · Linux in RHC2 (Ubuntu 24.04, Debian 12+)
The left tree shows the USB-attached controller (“USB; CAN ID 1”) as the bridge, a “CAN Bus” node under it, and the other controllers on that bus as children.
Out-of-date rows carry a red triangle; Update All and Check for Updates sit on top with “Last check: 10:55 am”; the Update tab shows Current 1.5.0, Latest 1.5.2, Out-of-Date.
IO diagnostics takes: Exactly our fdcanusb › CAN bus › node shape, the Current / Latest block, and the last-check time beside the update button.4,23
8. Intel RealSense Viewervendor
github.com · Apache-2.0 · Linux and Windows binaries
Devices appear on plug-in; the card title carries the USB generation (“3.2”), then Record · Sync · Info · More.
“Firmware Update Recommended!”: model, serial, Current vs Recommended version, one sentence on why, Install / Dismiss. Bottom bar counts errors, warnings, info.
IO diagnostics takes: Link speed beside the name (it catches a D405 on USB 2), “recommended” rather than “latest”, and Install / Dismiss as the shape of the update action.4,5,24
A small Device info card (model, maker, “Connected via Zigbee Coordinator”), readings first, then a separate Diagnostic card with an Identify button; noisy diagnostics are off by default.
Underneath: the device registry (manufacturer, model, serial_number, sw_version, hw_version, via_device) and the Update entity (installed_version, latest_version, release_url, install).
IO diagnostics takes: The identity schema word for word, via_device as the tree edge, the update entity as the update record, and button entities as the shape of a registered action.5,25,26
10. USB Device Tree Viewer (and Linux usbview)general
www.uwe-sieber.de · Windows freeware · Linux counterpart usbview, GPL-2.0, GTK
My Computer › host controller › root hub › port › device, each node resolved to a human label (COM port, product name).
A failed device gets a numbered problem badge (43) and a generic VID_0000&PID_0002; the detail pane shows the port chain, connection speed and a missing-serial warning.
IO diagnostics takes: The OS half of our tree: USB topology from sysfs, the port chain as “where is it plugged in”, and link speed plus enumeration failures as wire diagnostics.5,27,28
Also surveyed: 42 more tools, with links (click to open)
From the three survey lanes3,4,5. Every fact was read from a primary source on 2026-10-22; the lanes say where a page could not be reached.
① The chip and its Recent list. ② The window: tree on the left (icon · status · name · id/count, worst child rolls up), detail on the right with only the buttons a driver registered. ③ The activity log. ④ Table mode: the same rows with aligned columns. Device names and states are the real Oct 21 rack; values not known are drawn as “…” or “·”.1,32
Shape status two ways, never by color alone: a filled circle is OK, a triangle is WARN, a square is ERROR, a dashed ring is STALE or “no driver”. Tuner X repeats every color as a sentence; medkit uses disc, ring and square3,4.
The two planes: read-only status, driver-owned actions
Status flows one way and cannot disturb anything. Actions exist only if a driver registered them, and the driver, which already owns the port and the protocol, executes them. The host never sends a device command itself.1
This answers “is this read only or do we also allow commanding?” with both, kept apart. Off-the-shelf twins already do this: Home Assistant button and update entities, ros2_medkit’s Operations tab, and DroneCAN’s Restart / Update firmware beside the node’s identity1,4,5,15. Staleness is computed, not reported: each record declares its period, and one older than about 3× that period turns STALE, ROS diagnostics’ fourth level1,3.
Ownership: whoever owns the port does the talking
Case A is the normal one once the stack runs. Case B covers the bench before anything runs; “blocked: bus busy (tview pid 640623)” is a state the window shows, not an error it hides.1,32
Permissions reduce to a rule each driver already obeys: one owner per port, the same move as a single writer per piece of state. The holder check reads /proc/locks and never takes the lock itself, because a registry that test-acquires the rack lock makes an experiment refuse to start (measured in round 1)36.
The contract
ROS DiagnosticStatus has been stable since ROS 1 and every viewer in the survey reads it3,12. It lacks four things: a tree position, a staleness period, structured identity and update state. Three fields and one optional record add them, borrowed rather than invented1,5,25,26.
Field
Meaning
From
Note
level
OK · WARN · ERROR · STALE
ROS DiagnosticStatus
Kept. STALE is set by the host, never sent.
name
the check, e.g. comms, firmware
ROS DiagnosticStatus
Kept. Viewers key a row by (hardware_id, name).
message
one human sentence
ROS DiagnosticStatus
Kept; capped at 5,000 characters as Lichtblick does.
hardware_id
device identity: serial, CAN id, USB path
ROS DiagnosticStatus
Kept; the search key.
values[key, value]
string pairs: rates, counters, config
ROS DiagnosticStatus
Kept. Key conventions per kind, e.g. rate_hz, rate_expected_hz, replies.
path
host/usb-1-3/fdcanusb-272813EF/can/15
added
Tree position; replaces the aggregator’s YAML.
expected_period_s
how often a fresh record must arrive
added
Host marks STALE when a record is older than about 3× this.
Update available is a WARN, never an ERROR (ros2 doctor).
One record, written as JSON by a plain Python dataclass. No ROS needed to emit it; a ROS node or a foxglove-sdk bridge can republish it later3,12,20. Values marked <…> are filled by the driver.
low · medium · high; the verbs stay unequal: clear faults < restart < factory reset
preconditions
e.g. port free, rack lock free, motor disabled
confirm
the sentence the dialog shows before it runs
A button appears only if its driver registered it; the driver runs it; every action is confirmed and blocked while someone else holds the port.
Two sources build one tree
Grey rows come from the OS and exist even with no driver; teal rows come from each driver’s diagnostics and hang under their device through identity.via_device. An unknown device, like the C920e, is still useful: presence, port, VID:PID, serial. Rows from the Oct 21 snapshot and that evening’s reports.1,32
This is “which USB port is it plugged into”, answered by the OS (sysfs, /dev/serial/by-id, kernel log), the same skeleton USB Device Tree Viewer draws; REV’s tree shows the driver half, a USB bridge with a CAN bus and its nodes beneath4,5,27. Two traps from round 1: a D405’s USB descriptor serial differs from its SDK serial (XXXXXXXXXXXX vs 2186222…), and with two adapters plugged the generic /dev/fdcanusb symlink can silently name the wrong one, so the tree keys adapters by serial6,36.
The computer diagnoses its own wires
Your idea, made first-class: every edge in the tree carries its own health, from passive sources1,2.
Four real edges from Oct 21. Green holds, amber is degraded, red dashed is broken. The camera mount counts as a wire too: a mechanical connection that drifted.1,32
Edge
Passive signals
Seen on Oct 21
USB port → device
re-enumerations per port per hour, disconnects, link speed (a D405 on USB 2 instead of 3.2), enumeration failures
D405 …4411 dropped 4 s; XXXXXXXX enumerated 5 times in 17 s
adapter → CAN bus
bus state (error-active → passive → bus-off), error counters, ACK-less frames, bus voltage dips
XXXXXXXX: adapter fine, bus dead
bus → node
reply loss per node, uptime resets (brown-outs), latched faults
CAN 15 fault 33 on bridge enable
mount → sensor
calibration drift (a mechanical wire)
D405 …6036 moved 28 px
The kernel’s SocketCAN counters give the CAN vocabulary for free on any netdev adapter, but an fdcanusb is a USB-serial device with no kernel counters, so its bus health has to come from the moteus driver or round-trip checks4,30. Kvaser CanKing’s Bus Statistics widget is close to the panel we want:4,29
Kvaser CanKing 7 (Kvaser’s own half-light, half-dark render): bus load, totals and per-second rates, error counters, and On Bus / Error Passive / Overrun lamps.
The activity log, seeded with Oct 21:
Oct 21
Event
What
Port
Source
06:14:42
plugged
fdcanusb XXXXXXXX (every device appears at 06:14)
1-3
kernel log
12:16:54
unplugged
RealSense D435 XXXXXXXXXXXX
6-2
kernel log
12:22:41
plugged
RealSense D405 XXXXXXXXXXXX (…4411)
6-2
kernel log
18:07:08
re-enumerated
D405 …4411: dropped off USB for 4 s, came back
6-2
kernel log, coalesced
18:11:42
plugged (flapping)
fdcanusb XXXXXXXX: 4 flaps on 1-7 (5 enumerations in 17 s), then settled
1-5
kernel log, coalesced
18:20:46
unplugged
fdcanusb XXXXXXXX
1-5
kernel log
18:29
blocked
fdcanusb XXXXXXXX: open by pid 640623 (tview); checks wait
1-3
/proc/locks + holder check
Oct 21
fault entered
CAN 15: fault 33 on bridge enable
1-3 › can
reported by the rack session
Oct 21
bus dead
XXXXXXXX: 0 of 4 boards answer; adapter fine, ACK-less bus
1-5 › can
reported by the rack session
Oct 21
moved
D405 …6036: moved 28 px since calibration
6-1
bam_rack_twin calibration
19:02
no replies
XXXXXXXX: 0 of 3 boards answer (supply off)
1-3 › can
reported by the rack session
The six kernel-log rows and the 18:29 holder are read straight from out/snapshot-live.json32; the coalescer folds XXXXXXXX’s five enumerations into one notice6. The four “reported” rows come from the rack session that evening1,6, with times only where a time was recorded.
Why it is a stable sub-assembly
The software stack changes every week; the hardware IO surface and the contract do not. Software can join later with the same records, which is what ROS diagnostics was designed for.1,7
This is commitment 3 of your own DP - Diagonistics: “the diagnostic surface is stable”, the foundation tests, dashboards and the data flywheel build on7,8. It is also the board-farm move: the station is permanent and its self-test is part of the station, separate from whatever device is under test35. Software components can join later (out of scope now)1,2.
Open question: reuse Lichtblick as the dashboard?
The same records can feed two views. Only the top one has the parts that make this tool.1,3
Lichtblick (MPL-2.0)
IO diagnostics
Summary + detail of DiagnosticArray
yes, free, with stale timeout and hardware_id filter
tree + detail, roll-up
Presence, plug and unplug events
no
yes (kernel log, coalesced)
USB skeleton (which port)
no
yes
Update available, actions
no
yes (update record, registered buttons)
Top-bar chip
no (a separate app; no documented component embed)
yes
Needs
a foxglove-sdk WebSocket host (MIT, no ROS); unproven end to end
one small Python process
Recommendation: own the small chip, tree and actions; emit the standard records so Lichtblick, Foxglove and rqt work as second views, and prove the bridge with a ~30-minute spike1,3,11,20. This matches the July decision to own the shell and treat any viewer as a replaceable lens11.
Converged mock
Captured from the parallel builder’s clickable mock (prototypes/v6-converged/), replaying the real Oct 21 rack at 18:11, 18:25 and 19:02. Looked at for this report, not driven by it.
Tree mode at the 18:11 plug. The entity tree runs host › port › adapter › CAN bus › board › driver and motor, worst child rolling up. CAN 15 is selected: status with its declared period and STALE rule, identity as device_info, versions, the driver’s registered actions, comms, read-only config and values, each value tagged real, recorded, reported or simulated.Table mode at 18:25, while tview holds XXXXXXXX: nested first column, then aligned status, message, rate measured / expected, last seen, firmware and errors. The boards age toward STALE and the detail says checks and actions wait for the holder.Activity: the log (newest first, port on every entry), a one-cell-per-minute timeline per device, and the link-health table, the computer diagnosing its own wires.The action plane: Reset board on CAN 15 opens a confirm listing its preconditions, which the driver re-checks before sending. At 18:25 it is blocked: tview holds the port.
Decision surface
Settled
Scope: a hardware IO diagnostics dashboard, RealSense-Viewer level, that works before any robot stack runs.1,2
Layout: chip with Recent → tree + detail → table mode → activity log; not in Quick Settings.1,2
Two planes; read-only by default; a button only when a driver registered it; every action confirmed; blocked while the port is held.1
Ownership rule: whoever owns the port does the talking.1
Contract: DiagnosticStatus + path + expected_period_s + identity; update as Home Assistant’s update entity.1
Stack default: V2’s single small Python process with a local web window; the plugin is each driver package’s own diagnostics.py, found by entry point.1,6
Open, each with a recommendation and the default if you say nothing
1. Exact STALE ruleRecommend STALE when a record is older than max(3 × expected_period_s, 5 s). 3× is the spec; the 5 s floor matches rqt, Lichtblick and the aggregator, so a fast record does not flicker. Default: that rule.
2. Actions while the stack owns the portRecommend: show the button disabled with the owner named (“owned by pid …”) in v1; forward through the stack driver’s own service later. Default: disabled, owner named.
3. Where “latest version” comes fromRecommend a per-model table shipped inside each driver package, no network; “unknown” is OK-level; update available is WARN, never ERROR. Default: local table.
4. Active link checksRecommend passive signals and driver-reported counters, plus V2’s one gated comms check per newly settled adapter, only when the port and rack lock are free. Default: that.
5. Lichtblick spike now or after v1Recommend after the mock is accepted; it changes nothing in v1 because the records are already in its shape. Default: after v1.
Out of scope
Software components (nodes, planners, policies): later, with the same records.
The CAD / 3D view that paints these statuses onto links in the model.
Robot control or motion, fleet and cloud views, long-term history (that belongs in a time-series store).
Built by docs/build_design_report.py. Vault note: IO Diagnostics — Prior Art and Converged Design (2026-10-22). Repo: ~/io-diagnostics. Claude Code · Opus 5.5.