Appearance
Reference
Lookup surface for muxen-boat. Everything here is taken from the software as it ships: its own option parser for the command line, its compiled-in defaults, the installed systemd units, and the paths the package writes to.
Command line
muxen-boat [OPTION]| Short | Long | Argument | Default | Effect |
|---|---|---|---|---|
-h | --help | — | — | print the usage block on stdout and exit 0 |
-v | --verbose | — | off | verbose output for debugging |
-V | --version | — | — | print the application version and exit 0 |
-i | --interface | canX | can0 | the CAN interface to open |
-u | --udp | — | off | enable the UDP NMEA-0183 output |
| — | --udp-ip | IPv4 or IPv6 address | 192.168.0.50 | UDP destination address |
| — | --udp-port | port | 1060 | UDP destination port |
| — | --json-plain | — | pretty | compact single-line JSON output |
--udp-ip, --udp-port and --json-plain have no short form. An unknown option prints the usage block on stderr and exits 2.
--udp-ip is validated with inet_pton for both IPv4 and IPv6; a name is not resolved. --udp-port must parse as a number in 1–65535. --interface is truncated to the kernel's interface-name length.
The version string is produced at build time from git describe --tags --always --dirty.
Compiled-in values with no option
| Value | Setting | Meaning |
|---|---|---|
| CAN function | 9 | the function code the daemon presents when it transmits |
| CAN instance | 0 | its instance |
| CAN filter | accept all | can_id = 0, can_mask = 0; every frame is delivered to the dispatcher |
Startup output
The daemon prints its effective configuration before doing anything:
config: interface = can0
config: verbose = 0
config: canFunction = 9
config: canInstance = 0
config: udpEnable = 0
config: jsonFormat = plainudpIp and udpPort are printed only when udpEnable is 1.
Exit status
| Status | Cause |
|---|---|
| 0 | --help or --version |
| 1 | invalid --udp-ip or invalid --udp-port |
| 2 | unrecognised option |
The daemon otherwise runs until the main loop stops, which it does on a CAN read error (can: read error (errno …) at verbose level).
Environment
| Variable | Read by | Default | Purpose |
|---|---|---|---|
MUXEN_INTERFACE | muxen-boat.service | can0 | substituted into ExecStart as --interface |
Override it with a drop-in (systemctl edit muxen-boat). The daemon itself reads no environment variable — the unit turns it into a command-line argument.
systemd units
muxen-boat.service
| Directive | Value |
|---|---|
Description | Muxen CAN Reader |
PartOf | muxen.target |
After | mosquitto.service, sys-subsystem-net-devices-can0.device |
Wants | sys-subsystem-net-devices-can0.device |
ConditionPathIsDirectory | /sys/class/net/can0 — hardcoded, not derived from MUXEN_INTERFACE |
Environment | MUXEN_INTERFACE=can0 |
User / Group | muxen / muxen |
ExecStart | /usr/bin/muxen-boat --interface ${MUXEN_INTERFACE} --json-plain |
Restart / RestartSec | always / 30 |
WantedBy | muxen.target |
muxen-boat-init.service
| Directive | Value |
|---|---|
Description | Muxen CAN Reader (Pre Start) |
Type | oneshot, RemainAfterExit=yes |
After | mosquitto.service |
ExecStart | /usr/bin/sh -c "mosquitto_pub -t 'system/time' -m \"$(date --iso-8601=seconds --utc)\"" |
WantedBy | muxen.target |
It publishes one system/time message at boot so clients have a wall clock before the daemon's first tick. Its format is the +00:00 offset form, where the daemon uses the Z form.
Hardening
Both units are confined identically: NoNewPrivileges, ProtectSystem=strict, ProtectHome, ProtectProc=invisible, PrivateTmp, PrivateDevices, ProtectClock, ProtectKernelLogs, ProtectKernelModules, ProtectKernelTunables, ProtectControlGroups, ProtectHostname, LockPersonality, RestrictNamespaces, RestrictRealtime, RestrictSUIDSGID, RemoveIPC, RestrictNetworkInterfaces=lo, SystemCallArchitectures=native, a SystemCallErrorNumber=EPERM, a negative SystemCallFilter covering @clock @cpu-emulation @debug @module @mount @obsolete @privileged @raw-io @reboot @resources @swap, and a CapabilityBoundingSet that drops 22 capabilities including CAP_NET_ADMIN, CAP_SYS_ADMIN, CAP_SYS_TIME and CAP_SETUID.
The daemon reads the CAN link state but cannot change it: bringing an interface up or setting its bitrate needs CAP_NET_ADMIN, which is dropped.
Files
| Path | Content |
|---|---|
/usr/bin/muxen-boat | the daemon |
/usr/lib/systemd/system/muxen-boat.service | the daemon unit |
/usr/lib/systemd/system/muxen-boat-init.service | the boot-time clock unit |
/etc/mosquitto/conf.d/mosquitto-boat.conf | the broker's listeners |
/etc/nginx/snippets/muxen-ws-boat.conf | the /ws/boat reverse-proxy route |
/usr/share/bash-completion/completions/muxen-boat | bash completion |
The systemd unit directory is taken from systemd.pc where available and normalised to /usr/lib/systemd/system.
Broker configuration
/etc/mosquitto/conf.d/mosquitto-boat.conf, installed by the package:
listener 1883
protocol mqtt
listener 1884
protocol websockets
allow_anonymous true| Port | Protocol | Used by |
|---|---|---|
| 1883 | MQTT over TCP | the daemon and every other MUXEN service on the Brain |
| 1884 | MQTT over WebSocket | web interfaces, via the nginx route |
The package's postinst runs systemctl restart mosquitto.service on configure, so the listeners exist immediately after installation.
Reverse-proxy route
/etc/nginx/snippets/muxen-ws-boat.conf:
nginx
location = /ws/boat {
proxy_pass http://127.0.0.1:1884/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 15d;
}Include it from the site that serves the web interface. The $connection_upgrade variable is expected to be defined by a map in the server's http block; this snippet does not define it.
That site — and the map — come from the boat's UI interface package (muxen-interface-*), which ships one nginx site file per boat, defines map $http_upgrade $connection_upgrade { default Upgrade; '' close; } above its server block, and includes this snippet inside it. The include is unconditional there — no trailing * — so on a boat whose interface site includes muxen-ws-boat.conf, nginx will not start unless this package is installed.
MQTT interface
The daemon's own client:
| Parameter | Value |
|---|---|
| Host / port | 127.0.0.1 / 1883 |
| Client ID | muxen-boat |
| Keepalive | 5 s |
Subscriptions, issued on every successful connection at QoS 0:
| Filter | Purpose |
|---|---|
device/+/+/command | per-device commands |
device/+/+/reset | generic reset |
device/+/+/rtr/+ | generic frame request |
Nothing else is subscribed. The narrow filters are deliberate: on a Pi, a # subscription made about 69 % of received traffic messages the daemon then had to discard.
Every id in these topics is checked, and a message with one out of range is dropped: nothing goes on the bus. The function code and the instance must be 0–63, and the frame identifier of rtr/<frame>0–4095 — the widths of the CAN identifier fields. A channel in a command payload must be a JSON integer in the range its device page gives; a string ("5"), a float or a boolean is dropped too, even on a bloc8 command that does not need a channel. Earlier versions masked the topic ids and narrowed the channel, so device/1/64/command drove BLOC8 #0, device/65/0/reset reset device 1/0, and channel 261 on an interconnection or generic I/O was channel 5. MQTT has no error reply: a dropped command is printed only at -v, if at all.
Publications:
| Topic | Period | Payload |
|---|---|---|
device/<function>/<instance>/<subtopic> | on each decoded frame | { "data": …, "metadata": … } |
device/<function>/<instance>/error/<errorCode> | on each DTC frame | as above |
device/interface | 10 s | CAN link state, counters and bit timing |
system/time | 1 s | a bare ISO-8601 UTC string, no envelope |
app/boat/info | on each connection, retained, QoS 1 | daemon identity and features; online: false as last will and on clean exit |
Payload shape, metadata keys and per-device fields are in The device catalogue and the pages it links to.
UDP NMEA-0183 output
Off by default. --udp opens one datagram socket at startup to --udp-ip:--udp-port and keeps it for the process lifetime.
Each forwarded frame is emitted as one sentence framed $<body>*<CS>\r\n, where <CS> is the XOR checksum of the body in two uppercase hex digits. A sentence longer than the 1024-byte buffer is dropped rather than truncated. The UDP output never replaces the MQTT publication; it is in addition to it.
Which frames are forwarded is fixed at build time. Forwarded:
| Function | Frames |
|---|---|
| 0 Button | life, state |
| 1 Bloc8 | life, io, current1, current2, voltage |
| 3 Interconnection | life, state, current |
| 4 Group | life, state |
| 5 Battery | life, state, cycle |
| 6 Converter | life, state |
| 7 Motor | life |
| 8 Lighting | life |
| 10 Solar | life, state |
| 11 Wind turbine | life |
| 12 Navigation | all seven frames |
| 16 IMOCA keel | system, keel, capa (not wing) |
| 17 Generic I/O | life |
| 22 / 23 SFSP | receiver life and state, interface life |
Not forwarded: the common broadcast frames, bloc8 input0102 / input0405, solar charger/parameters, converter limit-ac-1 / limit-ac-2, generic I/O testcanhost, IMOCA keel wing, and every frame of functions 20, 21, 24, 25, 26, 27, 28, 30 and 32 — plus the daemon's own system/time and device/interface topics.
Package
| Field | Value |
|---|---|
| Package | muxen-boat |
| Section / Priority | misc / optional |
| Architecture | any, Multi-Arch: foreign |
| Depends | mosquitto, mosquitto-clients, muxen-systemd, plus ${shlibs:Depends} |
| Replaces / Breaks | boat (the former package name) |
| Build system | meson, via dh --buildsystem=meson |
| Build dependencies | debhelper-compat (= 13), meson, pkg-config, libmosquitto-dev, libglib2.0-dev, libjson-c-dev |
| dpkg triggers | activates muxen-restart-target, and nginx-reload: a running nginx reloads at the end of the transaction, so a changed muxen-ws-boat.conf is served at once |
The daemon links libcanmqtt and libstdmuxen — the shared MUXEN CAN and decoding libraries — and json-c, all resolved as system packages or as meson subprojects.
TypeScript package
| Field | Value |
|---|---|
| Name | @muxen/boat |
| Entry points | ., ./message-types, ./plugin, ./devices |
| Required peer | mqtt ^5.0.0 |
| Optional peers | vue ^3.3.0, pinia >=2.1.0 || ^3.0.0 |
| Node | >=18.0.0 |
| Build | tsup, emitting ESM, CJS and type declarations |
It shares its version and git tag with the daemon. Usage is in Web clients.
