Appearance
Configuration
On a commissioned boat, nobody configures this daemon by hand. What the navigation page shows — whether there is an engine page at all, whether AIS targets appear on the chart — was decided when the boat was commissioned, written into one file, and the daemon reads it at startup.
The crew-visible consequence is simple: if a page is empty, the module behind it was probably never turned on. That is a configuration question, not a fault.
The rest of this chapter is for whoever does the turning on.
Three layers, in this order
The effective configuration is resolved once, at startup, from three layers:
- Built-in defaults. Every report module off; navigation and motor in 1 Hz mode; AIS track depth 10; route waypoint limit 500; wind damping 4; MQTT on, pointing at
127.0.0.1:1883, prefixnmea. - The deploy configuration,
/etc/muxen/deploy.json, written at commissioning. Overrides the defaults. - The command line. Overrides both.
--report-all is applied after all three and forces every report module on, so it always wins.
There is deliberately no fourth layer: the daemon reads no environment variables of its own beyond systemd's RUNTIME_DIRECTORY and STATE_DIRECTORY. Environment files exist, but they feed the systemd unit's command line, not the daemon.
The deploy configuration
/etc/muxen/deploy.json is the boat's commissioning file, shared by every MUXEN service. It is a project export, not a file written for this daemon, so muxen-nmea2000 reads only one device out of it: the NavigationInstruments device (function code 12) at instance 0, which is device id 12 × 64 = 768. That device's parameters array carries the settings below.
| Parameter | Type | Equivalent flag | Effect |
|---|---|---|---|
ReportNav | bool | --report-nav | publish nmea/navigation |
NavHighSpeed | bool | --nav-high-speed | 10 Hz instead of 1 Hz |
WindDamping | int 0–9 | --wind-damping=N | wind smoothing, 0 = off |
ReportMotor | bool | --report-motor | publish nmea/motor |
MotorHighSpeed | bool | --motor-high-speed | 10 Hz instead of 1 Hz |
ReportThruster | bool | --report-thruster | publish nmea/thruster |
ReportAis | bool | --report-ais | publish nmea/ais |
AisTrackDepth | int | --ais-track-depth=N | track points kept per target |
ReportRoute | bool | --report-route | publish nmea/route |
RouteMaxWaypoints | int | --route-max-waypoints=N | waypoints kept per route |
DatetimeOffset | nmea or auto | --datetime-offset= | how local time is derived |
Booleans are stored as the strings "0" and "1" ("true" and "false" are also accepted, case-insensitively). A value that parses as neither is ignored with a warning naming the parameter:
Deploy: ReportMotor has invalid boolean value 'yes', ignoringAn absent parameter means "keep the built-in default". Unknown parameters are ignored.
Nothing in that list is required. All three of these are valid and none is an error:
- the file does not exist —
Deploy: /etc/muxen/deploy.json not found, skipping deploy layer - the file is
{}, or has no NavigationInstruments device —Deploy: no NavigationInstruments device in …, using defaults - the file exists but the device carries none of the parameters above
Two flags change where this layer comes from:
sh
muxen-nmea2000 --deploy-config /path/to/other.json # read a different file
muxen-nmea2000 --no-deploy-config # skip the layer entirely--no-deploy-config is what the templated unit uses, so a maintenance instance can never pick up the commissioned settings (More than one bus).
Precedence, worked through
deploy.json | command line | effective |
|---|---|---|
| absent | nothing | built-in default |
ReportMotor=1 | nothing | motor on |
ReportMotor=0 | nothing | motor off |
ReportMotor=0 | --report-motor | motor on — the CLI wins |
ReportMotor=1 | --report-all | motor on |
ReportMotor=0 | --report-all | motor on — --report-all is applied last |
One edge to know about. GLib's option parser does not report which options were actually present on the command line, so the daemon snapshots the affected fields immediately before parsing and treats any field that changed as "set by the CLI". A numeric option explicitly set to its own default — --ais-track-depth=10, say — is therefore indistinguishable from "not given", and the deploy layer can still override it. Deploys that carry explicit values are the normal case, so this is accepted rather than worked around.
Changing the unit's command line
muxen-nmea2000.service interpolates exactly two variables, both set in the unit itself:
ini
Environment="MUXEN_NMEA_INTERFACE=can1"
Environment="MUXEN_NMEA_NAME=0x11223344FF467788"The unit's ExecStart is:
/usr/bin/muxen-nmea2000 --interface=${MUXEN_NMEA_INTERFACE} \
--name=${MUXEN_NMEA_NAME} --enable-alarmsChanging either of those, or anything else — extra report flags, a different MQTT host, verbosity — means a systemd drop-in:
sh
sudo systemctl edit muxen-nmea2000ini
[Service]
ExecStart=
ExecStart=/usr/bin/muxen-nmea2000 --interface=${MUXEN_NMEA_INTERFACE} \
--name=${MUXEN_NMEA_NAME} --enable-alarms --report-nav --report-aisThe empty ExecStart= is required: without it systemd appends rather than replaces.
The shared /etc/muxen/env was removed from every MUXEN unit on 2026-09-01; overrides are drop-ins now. The per-instance /etc/muxen/env.<instance> is a different mechanism and is unaffected — it uses different variable names, so the primary daemon's NAME can never leak into an instance.
Navigation source configuration
Which GPS wins when there are two of them is a separate file with its own lifecycle, edited with muxen-nmea2000-config rather than by hand. It is covered in Navigation.
Its default location depends on how the daemon was started:
| Started as | Default path |
|---|---|
a systemd service (STATE_DIRECTORY set) | $STATE_DIRECTORY/nav-sources.json |
| root, outside systemd | /var/lib/muxen-nmea2000/nav-sources.json |
| an ordinary user | $XDG_CONFIG_HOME/muxen-nmea2000/nav-sources.json |
--nav-config PATH overrides all three.
Reloading without a restart
Navigation source configuration is re-read on SIGHUP. Nothing else is: report modules, the interface, the NAME and the MQTT settings are fixed for the life of the process.
sh
sudo systemctl reload muxen-nmea2000 # ExecReload=/bin/kill -HUP $MAINPIDOn SIGHUP the daemon logs Received SIGHUP, reloading configuration…, reloads the navigation source file, and republishes nmea/config.
A change to /etc/muxen/deploy.json needs a real restart. In normal MUXEN operation you get one for free: the package installs a muxen-restart-target trigger and the unit is PartOf=muxen.target, so a deployment restarts the target and the daemon with it. Editing the file by another route needs systemctl restart muxen-nmea2000.
Reading back what took effect
The daemon publishes its resolved configuration to nmea/config as a retained message, once when MQTT connects, again once all four virtual devices have claimed addresses, and again on every SIGHUP:
sh
mosquitto_sub -h 127.0.0.1 -t 'nmea/config' -vIt carries the state of every module, the AIS track depth, the route waypoint limit, the datetime offset source, the GPSD settings, the configured navigation sources with the bus address each currently resolves to, the topic prefix and the daemon version. Its expireAfterSec is 3124137600 — about 99 years — because it is a statement of configuration, not a reading.
That topic is the answer to "did my change take?". The journal's one-line summary at startup is the quick version of the same thing:
Config: can1 NAME=0x11223344FF467788 addr=0x80 (100-200) MQTT Alarms Nav AIS DateTime