Appearance
Reference
Everything the five tools expose, in tables. Nothing here is a procedure; the chapters before it are.
Common to all five
Defaults
| Setting | Value | Applies to |
|---|---|---|
| MQTT host | 127.0.0.1 | io, voltage, data-explorer, sfsp |
| MQTT port | 1883 | the same four |
| MQTT client id | <tool-name>-<hostname>-<pid> | the same four |
| CAN interface | can0 | devices, sfsp |
| Redraw period | 50 ms | io, voltage, devices |
| Redraw period | 100 ms | data-explorer |
muxen-diag-sfsp has no redraw: it writes lines as they arrive.
The client identifier is qualified with the host name and the process id so that several tools, on several machines, can hold connections to the same broker at once without the broker closing one for the other.
Behaviour shared by every option parser
| Rule | Effect |
|---|---|
-V, --version | print the build version and exit 0 |
--help | print the usage block and exit 0 |
| unknown option | print the usage block and exit 1 |
-p not a whole number, or outside 1…65535 | error: invalid port '<v>', exit 1 |
The version string is git describe --tags --always --dirty captured at build time, not the Debian package version.
--help is written to standard error by muxen-diag-io, muxen-diag-voltage, muxen-diag-data-explorer and muxen-diag-devices; muxen-diag-sfsp writes it to standard output.
MQTT connection
| Parameter | io | voltage | data-explorer | sfsp |
|---|---|---|---|---|
| Keepalive | 5 s | 5 s | 5 s | 60 s |
| Authentication | none | none | none | none |
| QoS on subscribe | 0 | 0 | 0 | 0 |
A failed initial connection is fatal: the tool prints error: cannot connect to MQTT broker at <host>:<port> and exits 1. There is no reconnection from that state.
Signals
In the four full-screen tools, SIGINT and SIGTERM leave the main loop cleanly, restore the terminal and exit 0. Q does the same. muxen-diag-sfsp runs on the loop supplied by libstdmuxen and is stopped with Ctrl-C.
Colour pairs
Used by muxen-diag-io, muxen-diag-voltage and muxen-diag-devices through the shared TUI layer:
| Colour | Meaning |
|---|---|
| green | live, healthy, or equal |
| red | fault, or an update available |
| yellow | stale, warning, or a pre-release firmware |
| grey background | the selected row |
On a terminal without colour support the selected row is drawn in reverse video and everything else in the default attributes.
muxen-diag-devices
Scan the CAN bus, list boards with their firmware version, act on one.
muxen-diag-devices [OPTIONS]Options
| Short | Long | Argument | Default | Meaning |
|---|---|---|---|---|
-i | --interface | canX | can0 | CAN interface to scan |
-V | --version | — | — | print the version and exit 0 |
--help | — | — | print the usage block and exit 0 |
No MQTT options. This tool does not use the broker.
Keys
| Key | Action |
|---|---|
| up / down | move the selection |
| PgUp / PgDn | move ten rows |
| Home / End | first / last row |
U | update the selected board's firmware |
C | clear the selected device's configuration |
R | reset the selected device — no confirmation |
P | park the selected device (instance → 63) |
I | set the selected device's instance number |
S | re-read the selected board's configuration |
E | rescan the bus and read only unknown boards |
Q | quit |
There is no ? overlay; the action keys are listed in the footer.
Columns
| Column | Content |
|---|---|
UID | 16 hex characters, shown on the first row of each board group |
Function | function name and code, e.g. Power output (1) |
Inst | instance 0…62, or P for instance 63 |
Product | product name, or (same hw) on a non-first row, or read failed |
Version | firmware version the board reports |
Latest | newest compatible firmware on disk, with a comparison marker |
Latest markers:
| Marker | Colour | Meaning |
|---|---|---|
= | green | the board is at the newest available version |
^ | red | a newer version is available |
= (Not released firmware, it's CE !) | yellow | the board is ahead of anything on disk |
- | — | no compatible firmware on disk |
Addressing
function = deviceId / 64
instance = deviceId % 64Instance 63 is the parking address and is displayed as P.
Commands it runs
Every one is executed directly, without a shell.
| Trigger | Command |
|---|---|
startup, [E] | muxen-uds -i <iface> uid --scan-to-json <tmp> |
startup, [E], [S] | muxen-uds -i <iface> -d <id> readconfig --only-name ProductId --only-name SoftwareVersion --save-as-json <tmp> |
[U] | muxen-uds -i <iface> -d <id> firmware --srec <path> |
[C] | muxen-uds -i <iface> -d <id> factory --reset-device-configuration |
[R] | muxen-uds -i <iface> -d <id> reset |
[P] | muxen-uds -i <iface> -d <id> factory --reset-device-id |
[I], on a parked board only | muxen-uds -i <iface> uid --uds --uid <uid> |
[I] | muxen-uds -i <iface> -d <id> writeconfig --name InstanceNum --value <n> |
[I] | muxen-uds -i <iface> -d <id> reset |
A non-zero exit status from any of them is reported in the status line.
[U], [C], [P] and [I] run with the table suspended and the command echoed; [U], [C] and [P] then wait for ENTER. The scan, the configuration read and [R] run silently with their output discarded.
Fixed waits: five seconds after a firmware update and after a park, for the board to reboot; one second between each step of [I].
Files
| Path | Use |
|---|---|
/usr/lib/muxen/firmware | firmware root. Not shipped by this package |
/usr/lib/muxen/firmware/products.json | product code → readable name |
/usr/lib/muxen/firmware/<code>/<code>-v<version>-<name>.srec | a firmware image |
/tmp/muxen-diag-devices-scan-XXXXXX | scan result, created with mkstemp, removed immediately |
/tmp/muxen-diag-devices-cfg-XXXXXX | configuration read result, same |
products.json is an array of {"code": …, "name": …}. A product with no entry is displayed by its code.
Firmware selection
- Only files whose name begins
<productId>-vare considered. - The version is the text between
-vand the next-. - Versions compare as
<major>.<minor>, numerically on each part; a version that does not parse never sorts above one that does. - If the board's version parses, only the same major is considered. A board on 5.1 is never offered 6.0.
- With no readable current version, the newest of any series wins.
Limits and exit codes
| Device rows | 128, then [!] table full (128 max) |
Products loaded from products.json | 128 |
| Minimum terminal width | 105 columns |
| Status message lifetime | 10 s |
| Exit | Cause |
|---|---|
| 0 | normal exit, --help, --version |
| 1 | unknown option, or Error: bus scan failed (is muxen-uds installed?) |
muxen-diag-io
Live table of Bloc 8 and Generic I/O outputs, digital inputs and analogue inputs.
muxen-diag-io [OPTIONS]Options
| Short | Long | Argument | Default | Meaning |
|---|---|---|---|---|
-h | --host | address | 127.0.0.1 | MQTT broker |
-p | --port | port | 1883 | MQTT broker port |
-V | --version | — | — | print the version and exit 0 |
--help | — | — | print the usage block and exit 0 |
Keys
| Key | Action |
|---|---|
| up / down | move the selection |
| PgUp / PgDn | move ten rows |
| Home / End | first / last row |
F4 | filter by name |
Esc | clear the filter |
? | legend |
Q | quit |
Subscriptions
| Topic | Feeds |
|---|---|
device/1/+/io | Bloc 8 digital and analogue inputs |
device/1/+/life | Bloc 8 output states and total current |
device/17/+/life | Generic I/O relays, inputs and analogue inputs |
Payload keys read
| Function | Topic | Key | Column |
|---|---|---|---|
| 1 | io | digitalInput0…digitalInput5 | D0–D5 |
| 1 | io | analogInput0…analogInput2 | A0–A2 |
| 1 | life | out0Code…out7Code | OUT0–OUT7 |
| 1 | life | currentTotal | read, not displayed |
| 17 | life | relay0, relay1 | REL0, REL1 |
| 17 | life | input0, input1 | D0, D1 |
| 17 | life | ana0, ana1 | A0, A1 |
Keys are looked up under a data object if the payload has one, and at the top level otherwise.
Output state codes
| Code | Shown | Colour | Meaning |
|---|---|---|---|
| 0 | .. | plain | power off |
| 1 | ON | green | power on |
| 2 | SC | red | short circuit |
| 3 | NC | yellow | not connected |
Staleness
| Rule | Value |
|---|---|
| Row goes yellow after | 30 s without a live update |
AGE shows * when | the last update was 1 s ago or less |
AGE shows - when | no live update has ever arrived |
A payload carrying metadata.rxTimestamp is treated as expired when now - rxTimestamp exceeds metadata.expireAfterSec (default 30). An expired payload updates the displayed values but not the age, so the row goes stale while messages keep arriving.
Bloc 8 input columns age on the io topic, independently of the output columns.
Display conventions
| Device label | Bloc8 #<instance+1> or GenIO #<instance+1> |
| Sort order | function, then instance |
| Filter | case-insensitive substring of the label; the topic is matched only if the needle contains / or device |
| Device limit | 64, then [!] table full (64 max) |
| Minimum terminal width | 123 columns |
Exit codes
| Exit | Cause |
|---|---|
| 0 | normal exit, --help, --version |
| 1 | unknown option, invalid port, or the initial broker connection failed |
muxen-diag-voltage
Live table of voltage, current and state for every power device.
muxen-diag-voltage [OPTIONS]Options
Identical to muxen-diag-io: -h, -p, -V, --help.
Keys
| Key | Action |
|---|---|
| up / down | move the selection |
| PgUp / PgDn | move ten rows |
| Home / End | first / last row |
D | show / hide the EXTRA column — on by default |
F4 | filter by name |
Esc | clear the filter |
? | legend |
Q | quit |
Functions and labels
| Code | Label | Code | Label |
|---|---|---|---|
| 1 | Bloc8 | 11 | WindTurb |
| 3 | Interco | 24 | MagicTrim |
| 4 | Group | 26 | CGS |
| 5 | Battery | 27 | Alternat |
| 6 | Converter | 30 | BatConc |
| 7 | Motor | ||
| 10 | Solar |
Subscriptions
device/1/+/life device/6/+/life device/26/+/life
device/1/+/voltage device/6/+/state device/26/+/state
device/3/+/life device/7/+/life device/26/+/state2
device/4/+/life device/10/+/life device/27/+/life
device/4/+/state device/10/+/state device/27/+/state
device/5/+/life device/11/+/life device/30/+/life
device/24/+/lifeWhich topic fills which columns
VOLT/CURR is the primary side, VOLT2/CURR2 the secondary, and EXTRA neither. A topic declares which of the three it feeds, so a state topic that carries only rpm does not announce a secondary electrical side that would then be drawn as 0.0.
| Function | Topic | Fills | Keys read |
|---|---|---|---|
| 1 Bloc 8 | life | primary | currentTotal → CURR |
| 1 Bloc 8 | voltage | primary | voltage → VOLT |
| 3 Interco | life | primary | outputTension, outputCurrent, countActiveBattery, countTotalBattery |
| 4 Group | life | primary | voltage, state, stateCode, temperature |
| 4 Group | state | secondary | voltage, current, rpm, power |
| 5 Battery | life | primary | voltage, current, soc, temperature, state, stateCode |
| 6 Converter | life | primary | voltage, current, state, stateCode, temperature |
| 6 Converter | state | secondary | voltage, current |
| 7 Motor | life | primary | voltage, current, state, stateCode, temperature, rpm |
| 10 Solar | life | primary | voltage, current, state, stateCode, temperature |
| 10 Solar | state | secondary | voltagePanel, currentPanel |
| 11 Wind turbine | life | primary | voltage, current, state, stateCode, temperature, rpm |
| 24 MagicTrim | life | primary | voltage, current, temperature, rpm |
| 26 CGS | life | primary | voltage, current, motorTemperature |
| 26 CGS | state | extra only | motorRpm |
| 26 CGS | state2 | secondary | motorLineToLineVoltage, motorPhaseCurrent |
| 27 Alternator | life | primary | voltage, current, state, stateCode |
| 27 Alternator | state | extra only | rpm, temperature |
| 30 BatConc | life | primary | voltage, current, soc, temperature, state, stateCode |
Where both state and stateCode are listed, stateCode wins when both are present: devices publish state as a label and stateCode as the number the row colours by.
The battery's and the concentrator's state topics are deliberately not read — they carry charge limits, which are configuration rather than measurement.
Secondary sides
| Family | VOLT2/CURR2 is |
|---|---|
| Converter | the AC / inverter side |
| Solar | the panel side |
| Group | the internal generator |
| CGS | the motor windings |
Every other family shows -.
State names
| Family | Code → name |
|---|---|
| Battery, BatConc | 0 OK, 1 WARN, 2 ALARM |
| Converter | 0 OFF, 1 BULK, 2 ABSO, 3 FLOAT, 4 FAULT, 5 INV |
| Solar | 0 OFF, 1 BULK, 2 ABSO, 3 FLOAT, 4 FAULT |
| Group | 0 OFF, 1 CONTACT, 2 START, 3 ON, 4 STOP, 15 ERROR |
| Motor | 0 OFF, 1 REGEN, 2 DRIVE, 3 ERROR |
| Wind turbine | 0 OFF, 1 ON |
| Alternator | 0 OFF, 1 ON, 2 FAULT, 3 BULK, 4 ABSO, 5 FLOAT, 6 STOR, 7 EQUAL, 8 ASSIST, 9 LP |
An unmapped code is shown as ?.
Colours: red on FAULT / ERROR / ALARM, yellow on WARN, green on any other non-zero state, plain on OFF / OK. The CGS has no state.
EXTRA per family
| Family | Fields shown |
|---|---|
| Battery, BatConc | state, SOC:<n>%, T:<n> |
| Converter, Solar | state, T:<n> |
| Group | state, T:<n>, <n>rpm, <n>W |
| Motor, Wind turbine | state, T:<n>, <n>rpm |
| MagicTrim | T:<n>, <n>rpm |
| Interco | <n>/<n> bat |
| CGS | T:<n>, <n>rpm |
| Alternator | state, T:<n>, <n>rpm |
Display conventions
| Device label | <Family> #<instance+1> |
| Sort order | function, then instance |
| Stale after | 30 s |
| Device limit | 64, then [!] table full (64 max) |
| Minimum terminal width | 87 columns |
Exit codes as muxen-diag-io.
muxen-diag-sfsp
A running log of wireless switch activity. Line-oriented, not a table.
muxen-diag-sfsp [OPTION]Options
| Short | Long | Argument | Default | Meaning |
|---|---|---|---|---|
-i | --interface | canX | can0 | CAN interface to read |
-h | --host | address | 127.0.0.1 | MQTT broker — giving this switches the tool into MQTT mode |
-p | --port | port | 1883 | MQTT broker port |
-V | --version | — | — | print the version and exit 0 |
--help | — | — | print the usage block on stdout and exit 0 |
The two modes are exclusive: with -h the CAN interface is not opened; without it the broker is not contacted.
Output
All three lines go to standard output and are flushed immediately.
| Line | When |
|---|---|
switch <n>: button <b> pressed | a button changed to pressed |
switch <n>: button <b> released | a button changed to released |
receiver <n>: emulating <c> switch | the emulated-switch count changed |
receiver <n>: unknown switch detected <d> (0x<h>) | on every such frame, not only on change |
<n> is the device instance plus one; <b> is the button index plus one. Only buttons 1 to 4 are reported.
CAN mode
| Function 23 | SFSP interrupter — its life frame carries the buttons |
| Function 22 | SFSP receptor — life carries the emulated count, state the unknown switch id |
| Kernel filters | three, one per frame above, matched on broadcast identifier and source function across all instances |
A read error on the socket ends the program.
MQTT mode
| Topic | Read |
|---|---|
device/23/+/life | bp0…bp7, of which the first four are reported |
device/22/+/life | NbEmulatedInter |
device/22/+/state | id — the unknown switch identifier |
Keys are looked up under a data object if present, at the top level otherwise. On connection it prints mqtt: connected to <host>:<port> on standard error; on failure, mqtt: connection failed (rc=<n>).
Limits
| Instances tracked | 64 per family |
| Buttons reported | 4 |
| MQTT keepalive | 60 s |
muxen-diag-data-explorer
The whole MQTT topic tree with pretty-printed payloads.
muxen-diag-data-explorer [OPTIONS]Options
| Short | Long | Argument | Default | Meaning |
|---|---|---|---|---|
-h | --host | address | 127.0.0.1 | MQTT broker |
-p | --port | port | 1883 | MQTT broker port |
--no-deploy | — | off | ignore /etc/muxen/deploy.json, so no friendly device names | |
-V | --version | — | — | print the version and exit 0 |
--help | — | — | print the usage block and exit 0 |
Keys
| Key | Action |
|---|---|
| up / down | move the tree cursor |
| left | collapse, or jump to the parent |
| right / Enter | expand, or descend to the first child |
| PgUp / PgDn | page the tree |
| Home / End | first / last node |
j / k | scroll the payload pane |
/ | start a search; Enter runs it |
n / N | next / previous match |
Esc | clear the search |
p | pause / resume |
? | help overlay |
q, Ctrl-C | quit |
Subscriptions
| Topic | Branch |
|---|---|
device/+/+/+ | device |
app/sensor/# | app > sensor |
nmea/# | nmea |
All three branches are created at startup and expanded, as is the sensor level under app.
Tree shape
A topic of exactly the form device/<function>/<instance>/<leaf> is grouped three levels deep:
| Level | Label |
|---|---|
| function | <function name> (code <n>), or code <n> if the name is unknown |
| instance | <device name> (<instance>), or instance=<instance> |
| leaf | the last topic segment |
Any other topic under the three roots is split on / and each segment becomes a level, to a maximum of eight segments.
Function nodes sort by function code, instance nodes by instance number, everything else alphabetically.
Instances here are the bus instances, counted from 0.
Device names
Read from /etc/muxen/deploy.json at startup, unless --no-deploy was given.
| Looked up | Source |
|---|---|
| device entry | devices[], matched on function and instance |
| name | the Name parameter, falling back to Name.FR |
A missing file, an unreadable file or a device with no name leaves the instance level showing instance=<n>.
The payload pane
| Line | Content |
|---|---|
Topic: | the full topic, or (branch) on a non-leaf node |
Updated: | local time of the last reception, YYYY-MM-DD HH:MM:SS, or - |
| below | the payload, pretty-printed if it parses as JSON, raw otherwise |
Scrolling is clamped so the last line cannot be pushed off the top.
Pause
Messages arriving while paused are dropped, not queued. The title bar shows [PAUSED], or [PAUSED - <n> msg dropped] once any have been dropped. The counter is reset each time pause is entered.
Layout
| Tree pane width | 40 % of the terminal, minimum 20 columns, maximum width − 20 |
| Redraw period | 100 ms |
| Footer | topics: <n> — distinct topics seen since startup |
No minimum-width warning; the layout reflows instead.
Exit codes as muxen-diag-io.
Packaging
| Field | Value |
|---|---|
| Source package | muxen-diagnostic-tools |
| Binary package | muxen-diagnostic-tools |
| Architecture | any, Multi-Arch: foreign |
| Section | misc |
| Depends | muxen-uds (>= 9.8.2), plus the shared-library and misc substitutions |
| Homepage | https://www.muxen.fr |
The package installs no systemd unit, no configuration file, no maintainer script and no conffile.
Installed files
| Path | Content |
|---|---|
/usr/bin/muxen-diag-devices | the device manager |
/usr/bin/muxen-diag-io | the I/O explorer |
/usr/bin/muxen-diag-voltage | the voltage explorer |
/usr/bin/muxen-diag-sfsp | the wireless-switch log |
/usr/bin/muxen-diag-data-explorer | the topic-tree explorer |
<bash-completion completions dir>/muxen-diag-* | one completion per tool |
The bash-completion directory is taken from the bash-completion pkg-config file when it is available, and falls back to <datadir>/bash-completion/completions otherwise.
Files read, and not shipped
| Path | Read by | Shipped by |
|---|---|---|
/usr/lib/muxen/firmware/ | muxen-diag-devices | another package |
/etc/muxen/deploy.json | muxen-diag-data-explorer | the boat's deployment |
Building from source
sh
meson setup builddir
meson compile -C builddir
meson test -C builddir| Command | Effect |
|---|---|
make deb | build the Debian package |
make dev | a fresh build with the address sanitiser, then the tests |
make lint | reformat in place to .clang-format |
make lint-check | report formatting differences without changing anything |
make mrproper | remove every generated file, vendored libraries included |
Two test binaries run under meson test:
| Test | Covers |
|---|---|
firmware selection | version comparison and the same-major firmware rule |
view clamping | the shared list clamping used by every table view |
Dependencies: ncursesw, glib-2.0, json-c (with a subproject fallback), and the MUXEN libcanmqtt, libstdmuxen and libmuxenfile, each resolved as an installed package if present and as a Meson subproject otherwise. bash-completion is optional.
