Appearance
Deploying a boat configuration
A MUXEN installation is described by a project file: a JSON document listing the devices that should be on the bus and the parameter values each of them should hold. deploy writes that description onto the hardware; checkconfig compares the hardware against it without changing anything.
For the owner: this is the step that turns "the boat as designed" into "the boat as configured". It runs at commissioning, and again whenever the yard changes what a box is supposed to do. It is not something that happens during normal use.
The files
| Path | Role |
|---|---|
/etc/muxen/deploy.json | the default project file — the configuration of this boat |
/etc/muxen/configuration/<id>.json | a named project, selected with --project <id> |
/var/lib/muxen/deployed.json | written by deploy: which UID was found at which device id |
--filepath takes a path directly; --project raken-42 is shorthand for /etc/muxen/configuration/raken-42.json. Giving both means the last one on the command line wins.
deployed.json is the record of what was actually deployed, and it is published read-only over HTTP by the second nginx snippet this package installs: /etc/nginx/snippets/muxen-deployed.conf serves it at /api/deployed.json with Cache-Control: max-age=0, no-cache.
Checking before touching anything
sh
muxen-uds -i can0 checkconfig
muxen-uds -i can0 checkconfig --project raken-42
muxen-uds -i can0 -d 0x040 checkconfig # one device onlycheckconfig is read-only. It runs a UID scan to learn which devices are on the bus, then reads each device listed in the project and compares it row by row:
checkconfig: device-id 64
checkconfig expected: #004 VbatMin = 2800
checkconfig device: #004 VbatMin = 2600
checkconfig: device 320 offlinePer device it reports:
| Field | Meaning |
|---|---|
online | the device answered its configuration read |
mismatches | parameters present in both, with different values |
missingOnDevice | in the project, absent from the device |
missingInProject | on the device, absent from the project |
missingInProject ignores the five read-only identity parameters, so it lists settings the project does not manage rather than noise.
A device the UID scan did not see is reported online: false and is not read. --force skips that gate and attempts the read anyway, which is what you want when a device is known to be present but stays silent during the scan.
checkconfig is the dry run to reach for, because it is the one that tells you what differs. deploy --check-only is also read-only, but it answers the narrower question — does this device already match the project, yes or no — and stops at the first parameter that does not. See --check-only below.
Deploying
sh
muxen-uds -i can0 deploy
muxen-uds -i can0 deploy --project raken-42
muxen-uds -i can0 -d 0x040 deploy # one device onlyFor each device in the project file, in order:
- Skip it if the UID scan did not see it — unless
--force. - Factory-reset its configuration (
--reset-device-configuration: everything except the CAN address). - Wait 3 seconds for the device's flash write to settle.
- Write every parameter the project declares for it, through the normal
writeconfigpath — name-matched, type-converted, no-op writes skipped. - Write the project's e-paper legends for this device, one RoutineControl start per zone — routine 16 for every one of them, with a header naming the zone and a compressed copy of the zone's 155×40, 1-bit-per-pixel bitmap. The project stores that bitmap uncompressed, hex-encoded as 1600 characters (800 bytes); compressing it is what keeps the panel's LIN transfer near 400 ms instead of a second. An entry whose zone is below 16, or that carries no
data, is skipped: zones below 16 are UDS control routines (factory reset, locate, …), not display zones.--no-epaperskips this whole step;--check-onlyskips it too, through the same guard.--lang <REGION>selects each zone's<REGION>variant, falling back to the zone's bare (primary) entry where no such variant exists — except that a variant entry which exists but carries nodatadoes not shadow the bare entry, so a half-translated project still repaints every zone. With no--lang, only the bare entries are written. - Record the device id → UID pair in
/var/lib/muxen/deployed.json.
A legend failure does not undo the parameter write that already succeeded for that device: it is reported per zone in the result instead (see Reading the result), and deployed for a normal run still reflects the parameter write alone.
A deploy is therefore a replacement, not a merge: any setting that is not in the project file returns to the device's own default.
If -d selected a single device and that device is not in the project file, or was skipped, the command fails.
--check-only
sh
muxen-uds -i can0 deploy --check-only--check-only is a dry run. It walks the same project file and reads the same devices, but performs none of the steps that change anything:
| Step | Under --check-only |
|---|---|
| 1. UID scan gate | runs — it is a passive read |
| 2. Factory reset | skipped |
| 3. 3-second flash wait | skipped — there was no reset to settle |
| 4. Write the parameters | runs as writeconfig --check-only: compares, writes nothing |
| 5. Write the legends | skipped — same guard as --no-epaper |
6. Record in deployed.json | skipped — nothing was deployed |
The bus and /var/lib/muxen/deployed.json are therefore left exactly as they were found.
Per device the answer is a yes/no: writeconfig stops at the first parameter whose value on the device differs from the project. Use checkconfig when you want the full list of differences rather than the first one.
--epaper-only
sh
muxen-uds -i can0 -d 0x051 deploy --epaper-only --lang FR--epaper-only repaints a device's e-paper panel from the project without touching its configuration — the command for "switch the panel language" or "fix a legend" against a device that is already commissioned. It keeps step 1, the UID scan gate, and step 5, the legend push, and skips everything else:
| Step | Under --epaper-only |
|---|---|
| 1. UID scan gate | runs — it is a passive read |
| 2. Factory reset | skipped |
| 3. 3-second flash wait | skipped — there was no reset to settle |
| 4. Write the parameters | skipped entirely — no parameters are even read from the project |
| 5. Write the legends | runs |
6. Record in deployed.json | skipped — the device's configuration was not touched |
Because step 4 never runs, deployed for a device under --epaper-only does not mean "the parameters matched" — there were none to check — but "every selected legend zone made it to the panel": true when the device has no failed zone (including the case where the project declares no legend for it at all), false otherwise. The same substitution reaches the process exit code when -d selects a single device, and the rc of the terminal deployed notify over the WebSocket (0 when every selected legend succeeded, -1 otherwise — there is no parameter-write rc left to report).
--epaper-only is rejected together with --no-epaper, since nothing would be left to do, and together with --check-only, since a dry run already writes nothing — on both front-ends:
cmd: --epaper-only and --no-epaper are contradictory
cmd: --epaper-only and --check-only are contradictoryReading the result
On the command line, deploy prints the underlying commands' output — the UID scan table, then a writing: … line per parameter actually changed. The final cmd: deploy device-id=0x000 rc=0 line reports the overall result, and the exit status is 0 on success.
Over the WebSocket, deploy streams progress before its response:
json
{ "type": "notify", "notify": "abc", "detectedDeviceCount": 7 }
{ "type": "notify", "notify": "abc", "deviceId": 64, "state": "resetting to factory" }
{ "type": "notify", "notify": "abc", "deviceId": 64, "state": "deploying" }
{ "type": "notify", "notify": "abc", "deviceId": 64, "state": "writing legend", "zone": 20 }
{ "type": "notify", "notify": "abc", "deviceId": 64, "state": "writing legend", "zone": 24 }
{ "type": "notify", "notify": "abc", "deviceId": 64, "state": "deployed", "rc": 0 }A checkOnly run streams the same sequence without the resetting to factory state, which no longer corresponds to anything it does, and names the other two checking and checked; it also carries no writing legend notify, since checkOnly skips the legend step along with everything else that writes. --no-epaper skips only the legend step, so its sequence keeps resetting to factory / deploying / deployed but drops writing legend. --epaper-only keeps the deploying / deployed notify pair too, even though it writes no parameters — only resetting to factory, which it also skips, is missing.
One writing legend notify is sent per selected zone, immediately before that zone's RoutineControl transfer starts. It is both the per-zone progress line and the liveness proof: like any other progress notification it re-arms a WebSocket client's idle timeout. A legend transfer is an 804-byte RoutineControl request — the 0x31 service byte, the action, the 16-bit routine id, and the 800-byte bitmap — segmented over ISO-TP like any other command, and is covered by the same timeout as the rest — 2000 ms over 3 attempts (see CAN transport), not the firmware transfer's own timeouts. A bench measurement in internal/epaper-bpu8s.md puts a single zone at roughly 160 ms, comfortably inside that margin.
It answers with one entry per device:
json
[ { "deviceId": 64, "deployed": true },
{ "deviceId": 128, "deployed": true, "legendsFailed": [20, 24] },
{ "deviceId": 320, "deployed": false, "error": "offline" } ]legendsFailed lists the zones whose RoutineControl transfer did not come back rc == 0; it is present only when at least one selected zone failed. On a normal run it does not change deployed, which by then can only reflect a successful parameter write — a failed one already skipped the device above. A checkOnly run never carries legendsFailed at all, since the legend step does not run under it either. Under --epaper-only, where there is no parameter write to reflect, legendsFailed (or its absence) is what deployed reports instead; see --epaper-only above.
error is offline when the UID scan did not see the device, factory reset failed when step 2 did not go through, or — under checkOnly only — out of sync when the device answered but its configuration does not match the project. Under checkOnly, deployed: true means "already matches the project"; nothing was written to get there.
checkconfig streams { "deviceId": …, "state": "checking" } the same way, preceded by the same detectedDeviceCount update.
Commissioning order
The order that works on a fresh installation:
- Address first.
uid --scanuntil noXappears and no unit is parked at instance 63. Deploying onto a contended address writes to the wrong box, or to neither. See Devices, addressing and discovery. - Firmware next. A firmware update can change parameter identifiers and resets configuration; do it before the settings, not after. See Firmware.
- Then deploy, and confirm with
checkconfigthat the bus matches the project.
Paths and the WebSocket
Over the WebSocket, filepath and project are rejected if they start with / or contain ... A WebSocket client can therefore select a project inside /etc/muxen/configuration/ but cannot point the daemon at an arbitrary file. The CLI applies no such restriction — it runs as the user who typed the command.
Also note that over the WebSocket the device is selected by the top-level deviceId field of the request, not by a device-id entry inside parameters; the latter is ignored by the daemon. See WebSocket protocol.
The project file schema
The project file is parsed by the shared libmuxenfile library, not by muxen-uds itself. What muxen-uds requires of it is:
- a list of devices,
- a device id per entry,
- a list of
{ id | name, type, value }parameters per device, in the same shapereadconfig --save-as-jsonproduces, - an optional per-device
legendsarray of{ zone, lang?, data }entries — the e-paper bitmapsdeploywrites after the parameters (step 5 above).
The authoritative schema lives with libmuxenfile; see internal/open-questions.md.
