Appearance
Getting started
Prerequisites
On the Brain:
- A MUXEN CAN interface that is up. The service is wired to
can0and refuses to start without it: the unit carriesConditionPathIsDirectory=/sys/class/net/can0. If the boat uses a different interface name, see Using another interface below. - mosquitto, and mosquitto-clients for the
mosquitto_subandmosquitto_pubcommands used throughout this manual. Both areDepends:of the package, soaptpulls them in. - muxen-systemd, which provides
muxen.targetand themuxenuser the daemon runs as. Also aDepends:.
Install
sh
sudo apt install muxen-boatThe package installs one binary, /usr/bin/muxen-boat, two systemd units, the mosquitto listener configuration, an nginx snippet for the WebSocket route, and bash completion. Its postinst restarts mosquitto, so the broker comes back with the boat listeners already configured.
Nothing needs enabling by hand: both units are WantedBy=muxen.target.
Verify it is running
sh
systemctl status muxen-boat.service
journalctl -u muxen-boat -n 30A healthy start prints its configuration summary first:
config: interface = can0
config: verbose = 0
config: canFunction = 9
config: canInstance = 0
config: udpEnable = 0
config: jsonFormat = plaincanFunction and canInstance are the address the daemon itself presents when it transmits on the bus. They are fixed at 9 and 0 and there is no option to change them.
If the service is looping instead, it restarts every 30 seconds (Restart=always, RestartSec=30) — see Troubleshooting.
Watch the bus
The fastest confirmation that the bridge is alive is the clock, because it does not depend on any device answering:
sh
mosquitto_sub -h 127.0.0.1 -t 'system/time' -v
# system/time 2026-06-17T14:30:45ZOne line per second means the daemon is running and connected to the broker.
Then the interface report, which arrives every ten seconds and says what the kernel thinks of the CAN link:
sh
mosquitto_sub -h 127.0.0.1 -t 'device/interface' -vThen the equipment itself:
sh
# everything the boat is saying
mosquitto_sub -h 127.0.0.1 -t 'device/#' -v
# one device: function 5 (battery), instance 0
mosquitto_sub -h 127.0.0.1 -t 'device/5/0/#' -vA payload looks like this:
json
{"data":{"state":"ok","voltage":12.8,"current":-15.4,"soc":87},
"metadata":{"rxdate":"2026-06-17T14:30:45.123Z","rxTimestamp":1781793045,"expireAfterSec":30}}data is the decoded frame. metadata says when it arrived and how long it stays meaningful. The service runs with --json-plain, so real payloads are on a single line.
Which function code corresponds to which equipment, and what each field means, is the device catalogue: The device catalogue.
Send a first command
Commands are small JSON documents published on the device's command topic. A light on channel 0 of the first lighting module:
sh
mosquitto_pub -h 127.0.0.1 -t 'device/8/0/command' -m '{"channel":0,"on":true,"dimming":50}'The daemon encodes that into a CAN frame and transmits it. There is no acknowledgement on MQTT — the confirmation is the device's own next life frame coming back with the new state, so watch the published topic in another terminal while you publish:
sh
mosquitto_sub -h 127.0.0.1 -t 'device/8/0/life' -vIf nothing changes, the command was either not understood or not acted on. The daemon silently drops a command it cannot encode — run it with -v to see why. See Troubleshooting.
Two commands work on any device regardless of its function, and take an empty payload:
sh
mosquitto_pub -h 127.0.0.1 -t 'device/3/2/reset' -m '' # reset device
mosquitto_pub -h 127.0.0.1 -t 'device/3/5/rtr/2' -m '' # ask for frame 2Reach it from a browser
The broker also listens for WebSocket clients on port 1884, and the package installs an nginx snippet that publishes it as /ws/boat:
nginx
include snippets/muxen-ws-boat.conf;Add that include to the site that serves the boat's web interface, and a page can connect to ws://<brain>/ws/boat. The @muxen/boat npm package does exactly that — see Web clients.
Using another interface
The unit sets MUXEN_INTERFACE, defaulting to can0. Override it with a drop-in:
sh
sudo systemctl edit muxen-boatini
[Service]
Environment="MUXEN_INTERFACE=can1"One thing does not follow that variable: the unit's ConditionPathIsDirectory=/sys/class/net/can0 is hardcoded, because systemd conditions cannot expand variables. On a Brain whose bus is not can0, that condition has to be adjusted too or the service will never start.
Running it by hand
Useful while commissioning, because the verbose output shows the handler tables and every decision the encoders make:
sh
sudo systemctl stop muxen-boat
/usr/bin/muxen-boat --interface can0 --json-plain -v--help lists every option; the full table is in Reference.
Where to go next
- What each device publishes and accepts — The device catalogue
- Building a screen on top of it — Web clients
- When a value is missing or wrong — Troubleshooting
- Every flag, path and unit — Reference
