Skip to content

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]
ShortLongArgumentDefaultEffect
-h--help——print the usage block on stdout and exit 0
-v--verbose—offverbose output for debugging
-V--version——print the application version and exit 0
-i--interfacecanXcan0the CAN interface to open
-u--udp—offenable the UDP NMEA-0183 output
—--udp-ipIPv4 or IPv6 address192.168.0.50UDP destination address
—--udp-portport1060UDP destination port
—--json-plain—prettycompact 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 ​

ValueSettingMeaning
CAN function9the function code the daemon presents when it transmits
CAN instance0its instance
CAN filteraccept allcan_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 = plain

udpIp and udpPort are printed only when udpEnable is 1.

Exit status ​

StatusCause
0--help or --version
1invalid --udp-ip or invalid --udp-port
2unrecognised 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 ​

VariableRead byDefaultPurpose
MUXEN_INTERFACEmuxen-boat.servicecan0substituted 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 ​

DirectiveValue
DescriptionMuxen CAN Reader
PartOfmuxen.target
Aftermosquitto.service, sys-subsystem-net-devices-can0.device
Wantssys-subsystem-net-devices-can0.device
ConditionPathIsDirectory/sys/class/net/can0 — hardcoded, not derived from MUXEN_INTERFACE
EnvironmentMUXEN_INTERFACE=can0
User / Groupmuxen / muxen
ExecStart/usr/bin/muxen-boat --interface ${MUXEN_INTERFACE} --json-plain
Restart / RestartSecalways / 30
WantedBymuxen.target

muxen-boat-init.service ​

DirectiveValue
DescriptionMuxen CAN Reader (Pre Start)
Typeoneshot, RemainAfterExit=yes
Aftermosquitto.service
ExecStart/usr/bin/sh -c "mosquitto_pub -t 'system/time' -m \"$(date --iso-8601=seconds --utc)\""
WantedBymuxen.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 ​

PathContent
/usr/bin/muxen-boatthe daemon
/usr/lib/systemd/system/muxen-boat.servicethe daemon unit
/usr/lib/systemd/system/muxen-boat-init.servicethe boot-time clock unit
/etc/mosquitto/conf.d/mosquitto-boat.confthe broker's listeners
/etc/nginx/snippets/muxen-ws-boat.confthe /ws/boat reverse-proxy route
/usr/share/bash-completion/completions/muxen-boatbash 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
PortProtocolUsed by
1883MQTT over TCPthe daemon and every other MUXEN service on the Brain
1884MQTT over WebSocketweb 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:

ParameterValue
Host / port127.0.0.1 / 1883
Client IDmuxen-boat
Keepalive5 s

Subscriptions, issued on every successful connection at QoS 0:

FilterPurpose
device/+/+/commandper-device commands
device/+/+/resetgeneric 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:

TopicPeriodPayload
device/<function>/<instance>/<subtopic>on each decoded frame{ "data": …, "metadata": … }
device/<function>/<instance>/error/<errorCode>on each DTC frameas above
device/interface10 sCAN link state, counters and bit timing
system/time1 sa bare ISO-8601 UTC string, no envelope
app/boat/infoon each connection, retained, QoS 1daemon 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:

FunctionFrames
0 Buttonlife, state
1 Bloc8life, io, current1, current2, voltage
3 Interconnectionlife, state, current
4 Grouplife, state
5 Batterylife, state, cycle
6 Converterlife, state
7 Motorlife
8 Lightinglife
10 Solarlife, state
11 Wind turbinelife
12 Navigationall seven frames
16 IMOCA keelsystem, keel, capa (not wing)
17 Generic I/Olife
22 / 23 SFSPreceiver 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 ​

FieldValue
Packagemuxen-boat
Section / Prioritymisc / optional
Architectureany, Multi-Arch: foreign
Dependsmosquitto, mosquitto-clients, muxen-systemd, plus ${shlibs:Depends}
Replaces / Breaksboat (the former package name)
Build systemmeson, via dh --buildsystem=meson
Build dependenciesdebhelper-compat (= 13), meson, pkg-config, libmosquitto-dev, libglib2.0-dev, libjson-c-dev
dpkg triggersactivates 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 ​

FieldValue
Name@muxen/boat
Entry points., ./message-types, ./plugin, ./devices
Required peermqtt ^5.0.0
Optional peersvue ^3.3.0, pinia >=2.1.0 || ^3.0.0
Node>=18.0.0
Buildtsup, emitting ESM, CJS and type declarations

It shares its version and git tag with the daemon. Usage is in Web clients.

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