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”.
BAM Robotics · IO diagnostics · design review · 2026-10-22

IO diagnostics: the converged design

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

  1. 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
  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
  3. The same tree flips to a table (status, message, rate measured vs expected, last seen, firmware, errors), and an activity log runs underneath.1
  4. 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
  5. 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

Converged mock, tree mode at the 18:11 plug
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 Registry contract window: device list left, '3 need you' detail rightClose-up of V4's list rows

V4’s window: a list on the left, detail on the right

“a nice simple layout... basically a bar on the left”
Zach, Oct 22

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: nested tree with path under each name, Diagnostic Details drawer on the right

Clearpath “All Diagnostics”: a tree with icon, status and name

“You have a left side hand.. that is essentially a tree.”
Zach, Oct 22

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 Quick Settings: Rack IO toggle beside Wired and Wi-Fi with a device listClose-up of V1's rows: XXXXXXXX 0 of 3 answer, D405 rows, OBSBOT new

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”
Zach, Oct 22

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 Hub row card: firmware, driver, config, bus, replies, and a Reset board confirm

V3’s button placeholder, and “how do printers do it?”

“I think we can have place holders for buttons”
Zach, Oct 22

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 Plug Journal tray: Recent section first, then one line per device

V2’s chip and Recent list: an activity log

“interesting idea here with like a activity log”
Zach, Oct 22

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 entity browser: component tree left, Operations tab with services and a Run buttonros2_medkit Entity Tree from the Open Robotics Discourse post

ros2_medkit: an Entity Tree, and Operations

“child parent relations you can do drop down”
Zach, Oct 22

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

NVIDIA Greenwave Monitor TUI: Topic, Message Type, Frequency, Expected Frequency, Status

Greenwave Monitor: a table with measured vs expected frequency

“turn it into more of like a table view for fast high level overview”
Zach, Oct 22

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 with Error Device, Warned Device, All devices and the history stripROS 1 Robot Monitor: (E: 1, W: 0) Computers : Error roll-up

rqt_robot_monitor: roll-up and a timeline strip

“Yes the roll up view I think important”
Zach, Oct 22

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 Add Source: D405 (USB3.2) with serial, platform camera (USB2.0)RealSense D405 card: USB 3.2 badge, Record, Sync, Info, More

RealSense Viewer: the level of ambition

“this is kinda at the level of like realsense viewer”
Zach, Oct 22

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.

Clearpath Cockpit ROS 2 Diagnostics screenshot

1. Clearpath Cockpit ROS 2 DiagnosticsROS-native

github.com · LGPL-2.1 · Linux (Cockpit plugin; needs ROS 2 + foxglove_bridge)

  • 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
ros2_medkit web UI screenshot

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
ROS diagnostics stack + rqt_robot_monitor screenshot

3. ROS diagnostics stack + rqt_robot_monitorROS-native

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
Foxglove / Lichtblick Diagnostics panels screenshot

4. Foxglove / Lichtblick Diagnostics panelsROS-native

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
CTRE Phoenix Tuner X screenshot

5. CTRE Phoenix Tuner Xvendor

v6.docs.ctr-electronics.com · Proprietary freeware · no Linux build (Windows, macOS, Android, iOS)

  • 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
DroneCAN GUI Tool screenshot

6. DroneCAN GUI Toolvendor

github.com · MIT · Linux, Windows, macOS (pip)

  • 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
REV Hardware Client screenshot

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
Intel RealSense Viewer screenshot

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
Home Assistant device page + registry screenshot

9. Home Assistant device page + registrygeneral

developers.home-assistant.io · Apache-2.0 · Linux (Python)

  • 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
USB Device Tree Viewer (and Linux usbview) screenshot

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.

LaneToolLicence · platformThe one thing worth taking
ROSrqt_runtime_monitorBSD-3 · ROS 2, QtFour severity buckets with counts; a real two-column key/value detail table.
ROSrqt_robot_dashboardBSD · ROS, legacyOldest top-bar-chip design: color = worst level, grey = stale, badge shape as a second channel.
ROSrosboardBSD-3 · ROSMobile-first card per topic; warns by example: device strings must be escaped.
ROSros2 doctorApache-2.0 · ROS 2Checks vs reports; one verdict line; “update available” is a WARN, never an ERROR.
ROSHusarion WebUIApache-2.0 snap · ROS 2A vendor reused Foxglove with a per-robot layout file instead of building a viewer.
ROSTurtleBot 4 diagnosticsApache-2.0 · ROS 2Stock publisher + stock rqt_robot_monitor; nothing custom.
ROSWebviz (Cruise)Apache-2.0 · abandoned 2022Origin of the Summary/Detail panels: Pinned / Stale / Error / Warn / Ok sections.
ROSdiagnostic_remote_loggingBSD-3 · ROS 2Long-term history goes to InfluxDB/Grafana, not the live panel.
vendormjbots moteus tview / moteus_toolApache-2.0 · Linux--info JSON is a ready identity record; flash = read version, back up config, flash, restore. No update check; fault is a bare integer.
vendorODrive GUI / odrivetoolFree hosted WebUSB app · Chromium on LinuxCard: model + S/N + “Firmware 0.6.9 – available: 0.6.9-1”; the update screen says what it will erase.
vendorDYNAMIXEL Wizard 2.0Freeware · Ubuntu 22.04 / 24.04Tree port › baud › model › ID; ID Inspection with blink-to-confirm; comms-error classes in a bottom strip.
vendormaxon EPOS StudioFree · WindowsTopology LocalHost › USB › node › CAN; Reset Node and Clear Errors as per-node verbs.
vendorCubeMars toolFreeware · WindowsStatus card spells out versions, bus volts, temperatures, mode and “no fault” in words.
vendorMyActuator GUI 2.1Freeware · WindowsThree-field status bar: Device Version | Motor State | Fault Code; a documented bit-flag fault table.
vendorSteadyWin GIMNo official tool foundNothing to borrow; ODrive-compatible drivers use the ODrive tools.
vendorStereolabs ZED DiagnosticFree with SDK · UbuntuChecklist starts with the host (driver, CUDA, GPU); each failed row says what to try; SAVE writes JSON for support.
vendorPEAK PCAN-ViewFree · Windows (Linux ncurses with PEAK driver)One-line status bar: link, bit rate, controller status, overruns; bus-load gauge with mean and max.
vendorKvaser CanKing 7Free · Windows, Linux (Kvaser drivers)Bus Statistics widget = the comms-check panel (shown in the wires section).
vendorSavvyCANMIT · Linux AppImageFrames per second as a cheap liveness number; frame-level is the wrong altitude here.
vendorCANable / SocketCAN countersLinux kernelERROR-ACTIVE / WARNING / PASSIVE / BUS-OFF plus error counters: the vendor-neutral bus vocabulary.
vendorTwinCAT / IgH EtherCATWindows / GPLv2 LinuxRequested vs current state; one line per device with a separate error flag; per-port carrier.
vendorVESC ToolGPL-3.0 · LinuxNot profiled (no README screenshot); CAN scan per forum reports.
generalViam appSaaS; viam-server AGPL-3.0Verdict chip with a fraction (“0 / 2 parts live”) opening an identity dropdown; network check “ran N ago”.
generalFormantCommercial SaaS + Linux agentPrint the ratio on the parent (“0/73 devices online”); small multiples of one metric across devices.
generalInOrbit ControlCommercial SaaSChecks × devices grid; OK is blue so “fine” is quiet; trouble sorts left.
generalFreedom RoboticsCommercial SaaS (status uncertain)One shared time bar with a LIVE chip; a deliberate “No alerts” empty state.
generalTransitive health monitoringApache-2.0 frameworkRow format `[pill] subsystem: message` expanding in place; zero-config ping monitor.
generalNVIDIA jtopAGPL-3.0 · Linux (Jetson)Identity and versions before any number; one daemon serving a TUI and a Python API.
generalNetdataGPL-3.0+ agentLive / Stale / Offline; a one-way plugin protocol with declared Functions as actions.
generalGrafana Node Exporter FullApache-2.0Each collector reports its own success and duration; collapsed rows say “(8 panels)”.
generalGNOME Firmware / fwupdLGPL-2.1 · LinuxParent/child device list, “No Releases Available”; record-and-replay device emulation for plugin tests.
generalmacOS System InformationmacOSShow More / Show Less depth switch; one saved report for support.
generaliStat MenusCommercial · macOSThreshold-for-duration rules so a blip does not alert.
generalStats (exelban)MIT · macOSOne popup skeleton for every module; connectivity grid, latency, jitter.
generalGNOME VitalsGPL-2.0 · GNOMECategory row = label + value + chevron in a 300 px dropdown; pin a reading into the bar.
generalIndicator-SysMonitorGPL-3.0 · LinuxTemplate string in the bar; any shell command as a sensor.
generalIgnition Gateway StatusCommercialNeutral tiles with “1 / 2 connected”, one red outline for the broken one; Generate Diagnostic Bundle.
generalArduino IDE 2 / arduino-cli / PlatformIOAGPL / GPL / Apache · LinuxPluggable discovery: HELLO / LIST / START_SYNC with add/remove events; match a board only when unambiguous.
generalParticle ConsoleCommercial cloud“Last vitals N ago” + refresh; a layered device › link › service health check.
generalbalenaCloud device statusesSaaS; openBalena AGPL-3.0Two independent link columns explain a degraded state; “N minutes in this state”.
generalUptime KumaMIT · self-hostedA banner sentence and a heartbeat strip per service.
generalKlipper status referenceLicence not checkedCAN bus_state active / warn / passive / off plus rx_error, tx_error, tx_retries per MCU.

Layout: chip → tree + detail → table → activity log

Wed Oct 22 09:41IO 3 ⚠13 need you · 5 devicesRECENT18:11 XXXXXXXX plugged on 1-5 (4 flaps)18:07 D405 …4411 dropped 4 s, backOpen IO diagnosticsIO diagnostics⌕ name, id, serial…TreeTable2▾rack-pc▾usb 1-3 · fdcanusb XXXXXXXXids 1,14,15▾can · error-active3 of 3CAN 1fw 1.1.2CAN 14fw 1.1.2CAN 15fault 33▸usb 1-5 · fdcanusb XXXXXXXX0 of 4▸usb 6-1 · D405 …6036moved 28 px▸usb 6-2 · D405 …4411usb 7-1.1.1 · Logi C920eno driverCAN 15 · moteusvia fdcanusb XXXXXXXX › canIDENTITYmjbots moteus · serial … · hw …FIRMWARE1.1.2 installed · 1.1.2 latest · currentCOMMSreplies 3 of 3 · last seen … · fault 33CONFIG · read-onlyratio, kp, kd … as the driver reportsIdentifyClear faults…← registered by moteus_servoACTIVITY318:29XXXXXXXXblocked: open by tview (pid 640623)18:20XXXXXXXXunplugged from 1-518:11XXXXXXXXplugged on 1-5 after 4 flaps on 1-718:07D405 …4411dropped off USB for 4 s, back on 6-24Table mode: same rows, alignednamestatusrate m/eseen▾ XXXXXXXX· / ··▾ can· / ··CAN 1· / ··CAN 14· / ··CAN 15· / ··XXXXXXXX· / ··D405 …6036· / ··D405 …4411· / ··C920e· / ··
① 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 PLANE · read-only · always onACTION PLANE · opt-in · driver-registered, driver-executedDevicefdcanusb XXXXXXXX› CAN 1, 14, 15speaks moteus protocolDriver packagemoteus_servo/diagnostics.pyowns the portstatus() → recordsactions:identify · clear_faultsreboot · update_firmwaredanger, preconditions, confirmIO diagnosticsrenders the treecomputes STALEshows a button onlyif it was registered;confirms, then asksthe driver to run ittelemetry, faultsrecords every periodregisters actionsrun clear_faults (confirmed)executes itnever: the host does not speak a device protocol
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

A · the stack is runningstack’s driver(moteus_servo)/dev/serial/by-id/…fdcanusb_272813EFownsIO diagnosticspure subscriberpublishes recordsopens nothing,cannot interfereB · nothing is runningIO diagnosticsport holder?rack lock held?/proc/locks + find_port_holdersfreestart the driver’s own checkas a short-lived owner,read, release the portheldshow it, do nothing“blocked: bus busy(tview pid 640623)”
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.

FieldMeaningFromNote
levelOK · WARN · ERROR · STALEROS DiagnosticStatusKept. STALE is set by the host, never sent.
namethe check, e.g. comms, firmwareROS DiagnosticStatusKept. Viewers key a row by (hardware_id, name).
messageone human sentenceROS DiagnosticStatusKept; capped at 5,000 characters as Lichtblick does.
hardware_iddevice identity: serial, CAN id, USB pathROS DiagnosticStatusKept; the search key.
values[key, value]string pairs: rates, counters, configROS DiagnosticStatusKept. Key conventions per kind, e.g. rate_hz, rate_expected_hz, replies.
pathhost/usb-1-3/fdcanusb-272813EF/can/15addedTree position; replaces the aggregator’s YAML.
expected_period_show often a fresh record must arriveaddedHost marks STALE when a record is older than about 3× this.
identitymanufacturer, model, serial_number, sw_version, hw_version, via_deviceHome Assistant device_infovia_device is the parent edge of the tree.
update (optional)installed_version, latest_version, release_urlHome Assistant update entityUpdate 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.

{
  "level": "ERROR",
  "name": "comms",
  "message": "CAN 15 fault 33 on bridge enable",
  "hardware_id": "fdcanusb-272813EF/can/15",
  "path": "host/usb-1-3/fdcanusb-272813EF/can/15",
  "expected_period_s": 1.0,
  "identity": {
    "manufacturer": "mjbots", "model": "moteus",
    "serial_number": "<from moteus_tool --info>",
    "sw_version": "1.1.2",
    "via_device": "fdcanusb-272813EF/can"
  },
  "update": {"installed_version": "1.1.2", "latest_version": "1.1.2"},
  "values": [["fault", "33"], ["replies", "3 of 3"]]
}

The action plane registers a second, separate record per action, shaped like Home Assistant button entities and ros2_medkit Operations1,5,15:

FieldMeaning
ididentify, clear_faults, reboot, update_firmware, run_comms_check
title, descriptionthe button label and one sentence on what it does
dangerlow · medium · high; the verbs stay unequal: clear faults < restart < factory reset
preconditionse.g. port free, rack lock free, motor disabled
confirmthe 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

from the OS: sysfs, /dev/serial/by-id, kernel log (always there, passive)from the driver’s diagnostics.py (hung under its device)rack PC (host)usb 1-3 · 12 Mb/s → fdcanusb XXXXXXXXmjbots · /dev/ttyACM0 · by-idcan · error-active · err 0 · warn 0moteus_servoCAN 1 · fw 1.1.2CAN 14 · fw 1.1.2CAN 15 · fw 1.1.2 · fault 33 on bridge enableusb 1-5 → fdcanusb XXXXXXXXplugged 18:11 after 4 flaps on 1-7can · 0 of 4 boards answer (adapter fine, bus dead)moteus_servousb 6-1 · 5 Gb/s → RealSense D405 (USB serial XXXXXXXXXXXX)/dev/video0-5colour + depth streams · SDK serial …276036 · moved 28 pxintel_rgbdusb 6-2 · 5 Gb/s → RealSense D405 (XXXXXXXXXXXX)dropped 4 s at 18:07colour + depth streams · SDK serial …4411intel_rgbdusb 7-1 › 7-1.1 › 7-1.1.1 → Logi Webcam C920eno driver diagnostics
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.

hostD405 …4411USB 6-2 · 5 Gb/s · re-enumerated once (18:07, 4 s gap)hostfdcanusb XXXXXXXX4 boardsUSB 1-7 → 1-5 · 5 enumerations in 17 sCAN · 0 of 4 reply · bus deadhostfdcanusb XXXXXXXXCAN 15 · fault 33USB 1-3 · 12 Mb/s · stableCAN · error-active · 3 of 3 replyrack frameD405 …6036mount, a mechanical wire · moved 28 px since calibration
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
EdgePassive signalsSeen on Oct 21
USB port → devicere-enumerations per port per hour, disconnects, link speed (a D405 on USB 2 instead of 3.2), enumeration failuresD405 …4411 dropped 4 s; XXXXXXXX enumerated 5 times in 17 s
adapter → CAN busbus state (error-active → passive → bus-off), error counters, ACK-less frames, bus voltage dipsXXXXXXXX: adapter fine, bus dead
bus → nodereply loss per node, uptime resets (brown-outs), latched faultsCAN 15 fault 33 on bridge enable
mount → sensorcalibration 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 Bus Statistics widget: bus load, Rx/Tx messages, error frames, error counters, On Bus / Error Passive / Overrun lamps
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 21EventWhatPortSource
06:14:42pluggedfdcanusb XXXXXXXX (every device appears at 06:14)1-3kernel log
12:16:54unpluggedRealSense D435 XXXXXXXXXXXX6-2kernel log
12:22:41pluggedRealSense D405 XXXXXXXXXXXX (…4411)6-2kernel log
18:07:08re-enumeratedD405 …4411: dropped off USB for 4 s, came back6-2kernel log, coalesced
18:11:42plugged (flapping)fdcanusb XXXXXXXX: 4 flaps on 1-7 (5 enumerations in 17 s), then settled1-5kernel log, coalesced
18:20:46unpluggedfdcanusb XXXXXXXX1-5kernel log
18:29blockedfdcanusb XXXXXXXX: open by pid 640623 (tview); checks wait1-3/proc/locks + holder check
Oct 21fault enteredCAN 15: fault 33 on bridge enable1-3 › canreported by the rack session
Oct 21bus deadXXXXXXXX: 0 of 4 boards answer; adapter fine, ACK-less bus1-5 › canreported by the rack session
Oct 21movedD405 …6036: moved 28 px since calibration6-1bam_rack_twin calibration
19:02no repliesXXXXXXXX: 0 of 3 boards answer (supply off)1-3 › canreported 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

Software stack: changes every weekrack runner, bam_rack_twin, ROS nodes later, planners, policiesjoins later with the same recordsIO diagnostics: chip · tree · table · activity log · registered actionsContract: DiagnosticStatus + path + expected_period_s + identityHardware IO surface: ports, adapters, buses, devicesstablechangingsecond viewsrqt_robot_monitorLichtblick · Foxglove
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?

driver recordsPython dataclass → JSONIO diagnostics (owned, small)chip · USB skeleton · plug events · update · actions · tree/detail30-min spikefoxglove-sdk WebSocket → Lichtblick Summary + Detailzero UI code; lacks presence, plug events, USB tree, update, actions
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 DiagnosticArrayyes, free, with stale timeout and hardware_id filtertree + detail, roll-up
Presence, plug and unplug eventsnoyes (kernel log, coalesced)
USB skeleton (which port)noyes
Update available, actionsnoyes (update record, registered buttons)
Top-bar chipno (a separate app; no documented component embed)yes
Needsa foxglove-sdk WebSocket host (MIT, no ROS); unproven end to endone 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.

Converged mock: hero
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.
Converged mock: table
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.
Converged mock: activity
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.
Converged mock: action
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

Open, each with a recommendation and the default if you say nothing

  1. 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. 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. 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. 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. 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

Source index

  1. Converged design spec, IO diagnostics (orchestrator, 2026-10-22)
  2. Zach, Daily Note Oct 22 2026, “Feedback on Io Diagonistics” (14 screenshots)
  3. Survey lane ROS: diagnostics dashboards in the ROS ecosystem (accessed 2026-10-22)
  4. Survey lane vendor: actuator, sensor and field-bus tools (accessed 2026-10-22)
  5. Survey lane general: device and health dashboards outside ROS (accessed 2026-10-22)
  6. Babble round 1 gallery: five top-bar directions (2026-10-21)
  7. DP - Diagonistics (Zach)
  8. MOC - Diagnostics (Zach)
  9. C - ROS Diagnostics Stack
  10. IoT Software Tools That Robotics Should Steal (Deep Research, 2026-07-16)
  11. Foxglove vs Rerun — Logging Boundary and Backend Decision (Deep Research, 2026-07-21)
  12. ROS 2 DiagnosticStatus.msg (common_interfaces, BSD-3)
  13. ros/diagnostics: diagnostic_updater + diagnostic_aggregator
  14. Clearpath Cockpit ROS 2 Diagnostics docs
  15. ros2_medkit (selfpatch)
  16. Open Robotics Discourse: What’s missing from ROS 2 diagnostics, and what we built
  17. NVIDIA Isaac ROS Greenwave Monitor (Apache-2.0)
  18. Foxglove Diagnostics panels docs
  19. Lichtblick (MPL-2.0)
  20. foxglove-sdk on PyPI (MIT)
  21. CTRE Phoenix Tuner X: Devices page
  22. DroneCAN GUI Tool (MIT)
  23. REV Hardware Client: SPARK MAX update
  24. RealSense Viewer SW/FW update logic
  25. Home Assistant device registry
  26. Home Assistant update entity
  27. USB Device Tree Viewer (Uwe Sieber)
  28. usbview for Linux (GPL-2.0)
  29. Kvaser CanKing
  30. Linux SocketCAN documentation
  31. Rockwell Automation completes acquisition of Clearpath Robotics (Oct 2, 2023)
  32. io_facts live snapshot, rack PC, 2026-10-21 18:29 (real kernel log + holders)
  33. rqt_robot_monitor
  34. ros2 doctor (ros2cli)
  35. C - Board Farm (Permanent Station, Swappable DUT)
  36. io-diagnostics README: round-1 measured traps

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.