Skip to content

Getting started ​

Prerequisites ​

On the Brain:

  • A MUXEN CAN interface that is up. The service is wired to can0 and refuses to start without it: the unit carries ConditionPathIsDirectory=/sys/class/net/can0. If the boat uses a different interface name, see Using another interface below.
  • mosquitto, and mosquitto-clients for the mosquitto_sub and mosquitto_pub commands used throughout this manual. Both are Depends: of the package, so apt pulls them in.
  • muxen-systemd, which provides muxen.target and the muxen user the daemon runs as. Also a Depends:.

Install ​

sh
sudo apt install muxen-boat

The 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 30

A healthy start prints its configuration summary first:

config: interface = can0
config: verbose = 0
config: canFunction = 9
config: canInstance = 0
config: udpEnable = 0
config: jsonFormat = plain

canFunction 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:45Z

One 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' -v

Then 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/#' -v

A 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' -v

If 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 2

Reach 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-boat
ini
[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 ​

Integration of multiplexed solutions
MUXEN and the MUXEN logo are trademarks of MUXEN SAS.