Skip to content

More than one bus ​

A boat normally has one NMEA 2000 backbone and one gateway daemon looking after it. Nothing in this chapter is needed for that boat.

It becomes needed in two situations, both of them an installer's rather than an owner's:

  • The boat has a second bus. A separate engine-room segment, a flybridge segment, a bus behind a gateway that is not the main backbone.
  • Someone needs to look at a bus without disturbing the boat. An engineer wants to enumerate the devices on a segment, or fix a device's instance number, on a bus the commissioned gateway is not responsible for — and must not risk changing what the boat's screens see while doing it.

Both are answered by the same mechanism: a second daemon, started on demand, on its own interface, under its own MQTT prefix.

The templated unit ​

muxen-nmea2000@.service is a systemd template. The text after the @ is the instance name, and the instance name is the CAN interface:

sh
sudo systemctl start muxen-nmea2000@can0
sudo systemctl status muxen-nmea2000@can0
sudo systemctl stop muxen-nmea2000@can0

There is nothing to enable. The template has no [Install] section and is not part of muxen.target, so instances never start at boot and never survive a reboot. They are started deliberately and stopped again.

The unit carries ConditionPathExists=/sys/class/net/%i, so an instance name that does not correspond to a real interface is skipped rather than started into a crash loop. systemctl status reports the condition as unmet.

The primary daemon, muxen-nmea2000.service, is unaffected by any of this and keeps running throughout.

Everything an instance owns is derived from its name ​

ResourcePrimaryInstance can0
CAN interfacecan1 (set in the unit)can0
MQTT topic prefixnmeanmea-can0
MQTT client idauto-generatedmuxen-nmea2000-can0
MQTT daemon infoapp/nmea2000/infoapp/nmea2000-can0/info
IPC socket/run/muxen-nmea2000/sources.sock/run/muxen-nmea2000-can0/sources.sock
Runtime directory/run/muxen-nmea2000/run/muxen-nmea2000-can0
State directory/var/lib/muxen-nmea2000/var/lib/muxen-nmea2000-can0
Environment file— (drop-in)/etc/muxen/env.can0

The single-instance lock lives in the runtime directory, so the "already running" check is per instance: the daemon prefers systemd's RUNTIME_DIRECTORY over its built-in /run/muxen-nmea2000 for both the lock and the IPC socket. Run by hand outside systemd it falls back to the built-in paths, and therefore still refuses to start twice.

A distinct MQTT client id matters more than it looks. Two MQTT clients connecting with the same id make the broker disconnect the first. Without --mqtt-client-id, an instance would repeatedly kick the primary daemon off the broker.

Instance names are restricted to letters, digits, - and _, because they are interpolated into both a filesystem path and an MQTT topic.

Sharing a bus with the primary daemon ​

An instance on a different interface needs no further thought. On the same bus as the commissioned daemon, three things make it safe.

1. It ignores the boat's configuration. The unit passes --no-deploy-config, so /etc/muxen/deploy.json is not read. An instance publishes the bus view and whatever its own command line asks for, and can neither inherit nor alter the commissioned settings.

2. It has a different J1939 NAME. Two identical NAMEs on one bus are illegal. The template ships 0x91223344FF467799 against the primary's 0x11223344FF467788.

3. It always loses arbitration. Two properties of that NAME make an address collision resolve in the primary's favour, every time:

  • The instance NAME is numerically higher. J1939 arbitration is won by the lower NAME, so the primary keeps its address.
  • The instance NAME has the arbitrary-address-capable bit set — that is the leading 0x9 rather than 0x1. The loser of an arbitration can only move to another address if that bit is set; without it, the loser fails outright with ACD: Failed to claim any address.

So the instance is always the one that relocates, and a maintenance instance can never dislodge the boat's gateway.

MUXEN_NMEA_INSTANCE_ADDRESS (default 140) is only a first-try hint that keeps the instance clear of the primary's 128–131. Each daemon claims one address per virtual device, four in total. If 140 is taken the daemon searches the dynamic range itself; starting both daemons at 128 on one bus lands the instance at 100–103.

Why not a random NAME ​

Only the identity number and one bit differ between the two NAMEs, and that is deliberate. The rest of the NAME is structured data — the manufacturer code, the device class, the function code — that other devices on the bus read to work out what this gateway is. A randomly generated NAME would misreport the product. A random identity number would also make the instance look like a brand-new device on every restart, churning the device list of every chartplotter on the bus.

Overriding per instance ​

sh
# /etc/muxen/env.can0
MUXEN_NMEA_INSTANCE_NAME=0x91223344FF4677AA
MUXEN_NMEA_INSTANCE_ADDRESS=150

These are deliberately not MUXEN_NMEA_NAME: the primary daemon's NAME is set in its own unit, and nothing in an instance needs it.

Working with an instance ​

Both tools take -I / --instance and derive the right socket or topic from it:

sh
muxen-nmea2000-inventory -I can0    # subscribes nmea-can0/devices
muxen-nmea2000-config    -I can0    # /run/muxen-nmea2000-can0/sources.sock

Without -I they talk to the primary daemon. The raw escape hatches remain: muxen-nmea2000-config -s <socket-path> and muxen-nmea2000-inventory -P <topic-prefix>.

And the data is under the instance's own prefix:

sh
mosquitto_sub -h 127.0.0.1 -t 'nmea-can0/#' -v

muxen-nmea2000-inventory ​

The quickest way to answer "what is on this bus?". It is a pure MQTT client — it reads the devices topic a daemon has already published and prints it. It never touches CAN and never claims an address, so it is safe to run at any time, including on a boat under way.

sh
muxen-nmea2000-inventory                 # the primary daemon
muxen-nmea2000-inventory -I can0         # an instance
muxen-nmea2000-inventory -j              # raw JSON
muxen-nmea2000-inventory -c              # semicolon-delimited CSV
OptionDefaultEffect
-H, --host HOSTlocalhostMQTT broker
-p, --port PORT1883MQTT port
-I, --instance NAME—read the muxen-nmea2000@NAME instance
-P, --prefix PREFIXnmeaMQTT topic prefix
-T, --timeout SEC5how long to wait for the retained message
-j, --jsonraw JSON
-c, --csvCSV, semicolon-delimited
-h, --helpusage

An empty list after the timeout means no daemon is publishing under that prefix — check -I and -P before concluding the bus is empty.

A typical maintenance session ​

sh
# 1. Confirm the interface exists and is up at 250 kbit/s
ip -details link show can0

# 2. Start a daemon on it
sudo systemctl start muxen-nmea2000@can0
journalctl -u muxen-nmea2000@can0 -n 30

# 3. See what is there
muxen-nmea2000-inventory -I can0

# 4. Fix a device instance, or set navigation priorities
muxen-nmea2000-config -I can0

# 5. Stop it again
sudo systemctl stop muxen-nmea2000@can0

Step 5 matters. An instance left running is a second gateway on the bus and a second set of retained MQTT topics, and neither is what the boat was commissioned with. Nothing brings it back after a reboot, so a forgotten instance survives only until the next power cycle — but it can confuse a colleague for a long time before then.

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