Appearance
Navigation
The navigation page on the boat's screens — position, heading, course and speed, wind, depth, autopilot — is drawn from a single MQTT message that this daemon rebuilds once a second.
Building it is not simply a matter of copying what arrives. A boat that carries two GPS antennas, a fluxgate compass and a satellite compass has four instruments answering two questions, and they will not agree. muxen-nmea2000 picks one source per kind of data and stays with it, rather than flickering between them, because a heading that jumps between two devices every second is worse than a heading that is slightly wrong.
Which one it picks is something an installer can set, per data type, per instrument. Out of the box the daemon uses a sensible rule and needs no configuration at all.
What gets published
Enable the report with --report-nav, or ReportNav in the deploy configuration.
- Topic
nmea/navigation - Rate 1 Hz, or 10 Hz with
--nav-high-speed/NavHighSpeed - QoS 0, retained
expireAfterSec2 — a consumer that sees a payload older than that should treat it as stale
The payload has a data object with up to seven sections, and a metadata object:
| Section | Contains |
|---|---|
position | fix state, latitude, longitude, altitude, datum, satellite and DOP figures |
speed_course | heading and its reference, COG, SOG, speed through water, rate of turn, set and drift |
wind_apparent | apparent wind speed and angle |
wind_true | true wind speed, angle and direction, and where they came from |
wind | deprecated, see Wind |
depth_water | depth, transducer offset, water temperature |
autopilot | steering mode, commanded heading, rudder |
A section is present only when a source for it is selected. position is the exception: if a GPS is transmitting but has no fix, the section is still emitted with hasFix: false, because "there is a GPS and it does not have a fix" is different information from "there is no GPS". wind_true is the other: it is present whenever wind_apparent is, because it can be computed from it.
The fields a consumer is most likely to need, with their exact paths:
| Path | Unit | Notes |
|---|---|---|
data.position.hasFix | boolean | always present in position |
data.position.latitude, .longitude | degrees | present only when hasFix is true |
data.speed_course.sog | knots | always present in speed_course, null when no source sends it |
data.speed_course.cog | degrees, true | always present in speed_course, null when no source sends it |
data.speed_course.speedThroughWater | knots | present only when a source sends it |
data.speed_course.headingTrue | degrees | present only when it can be resolved |
data.wind_apparent.speed | knots | null when not sent |
data.wind_apparent.angle | degrees, −180…180 | relative to the bow, starboard positive; null when not sent |
data.wind_true.speed | knots | null when not obtainable |
data.wind_true.angle | degrees, −180…180 | relative to the bow, starboard positive; null when not obtainable |
data.wind_true.direction | degrees, 0…360 | relative to true north, where the wind comes from; null when not obtainable |
metadata.expireAfterSec | seconds | 2 |
With --report-metadata-source, each section carries a source object naming the instrument the values came from — its J1939 NAME, its friendly name if one is configured, the PGN, the receive timestamp and the age of the reading.
metadata carries rxDate, rxTimestamp, expireAfterSec and configLoaded, which tells you whether a navigation source configuration file was found and parsed.
Two extra keys appear only under --verbose-nav: a sources array listing every source the daemon has seen with its per-data-type priorities and block flags, and unavailableTypes listing the data types nothing is currently supplying. They are diagnostic, not part of the normal contract.
How a source is chosen
The daemon keeps a registry of every device that has sent navigation data, keyed by J1939 NAME rather than by bus address, because the address can change and the NAME cannot. For each of the data types it tracks, it re-selects a source on every report.
A source is a candidate for a data type only if all of these hold:
| Condition | Meaning |
|---|---|
| not blocked | the installer has not disabled it for this data type |
| has sent data | at least one message of that type has arrived |
| at least 2 consecutive valid messages | a single stray frame does not promote a source |
| expiration counter below 5 | see below |
| warm-up complete | see below |
Among the candidates, the rule is:
- Lowest priority number wins. Priorities run 1–99 where 1 is the highest. A source with no configured priority is treated as 50, so an unconfigured boat has every instrument on equal footing.
- On equal priority, the freshest reading wins — but only if it is fresher by more than the data type's hysteresis window. Inside that window, the currently selected source keeps the job.
That hysteresis is the whole point. Two GPS antennas transmitting at the same rate will interleave their messages, and without it the selected source would change several times a second and the reported position would micro-jump between two slightly different fixes.
Warm-up
A source that has just appeared is not trusted immediately. Each data type has a warm-up period during which the source is a known candidate but not selectable.
Position has a two-phase warm-up: first it waits for the receiver to report an actual fix, and only then does the warm-up timer start. A GPS that has just been powered on and is still searching therefore never becomes the selected position source, no matter how loudly it transmits.
Expiration
Every data type has a time-to-live. A periodic check compares the age of each source's last reading against it:
- too old → the source's expiration counter goes up by one
- a fresh reading arrives → the counter goes down, and a global timer also decrements every counter every 10 seconds
At a counter of 5 the source stops being a candidate. This is deliberately a counter rather than a flag: an instrument that drops one message in ten is degraded, not dead, and it should lose to a healthy one without being thrown out of the running entirely.
A source that has not been heard from for 60 minutes is removed from the registry altogether.
Defaults per data type
| Data type | TTL | Hysteresis | Warm-up |
|---|---|---|---|
position | 5 s | 1000 ms | 10000 ms (after fix) |
heading | 5 s | 500 ms | 10000 ms |
course_speed | 5 s | 500 ms | 10000 ms |
wind (apparent) | 2 s | 300 ms | 5000 ms |
wind_true | 2 s | same as wind | same as wind |
depth_water | 5 s | 500 ms | 10000 ms |
autopilot | 5 s | 500 ms | 5000 ms |
Two further data types exist in the registry but are not selected the same way. date is tracked for source discovery — it is how a device that only sends time still shows up in the source list — and motor identifies J1939 engine sources, where the setting is an engine instance rather than a priority.
Wind
Apparent and true wind are two separate data types, each with its own source selection, because they often come from different places: the masthead unit measures apparent wind, while true wind may be sent by the same unit, by a sailing instrument display that computes it, or by nobody.
PGN 130306 carries one wind at a time, with a reference that says which. The daemon files each message by that reference:
| Reference | Meaning | Data type |
|---|---|---|
| 2 | apparent, relative to the bow | wind |
| 0 | true, relative to true north (a direction) | wind_true, ground |
| 1 | true, relative to magnetic north (a direction) | wind_true, ground |
| 3 | true, relative to the bow, over the ground | wind_true, ground |
| 4 | true, relative to the bow, through the water | wind_true, water |
References 5 to 7 are reserved or "not available" and are ignored.
wind_apparent
The apparent wind of the selected wind source, after damping.
| Field | Unit | Meaning |
|---|---|---|
speed | knots | apparent wind speed |
angle | degrees, −180…180 | apparent wind angle, relative to the bow, starboard positive |
source | as for every section, with --report-metadata-source |
wind_true
| Field | Unit | Meaning |
|---|---|---|
speed | knots | true wind speed |
angle | degrees, −180…180 | true wind angle, relative to the bow, starboard positive |
direction | degrees, 0…360 | true wind direction, relative to true north: where the wind comes from |
reference | "water" or "ground" | what the true wind is relative to |
computed | boolean | true when the daemon computed it, false when an instrument sent it |
source | with --report-metadata-source: the true wind instrument, or the apparent wind source when computed |
From an instrument. When a wind_true source is selected, its values are used. An instrument that sends both families is read as water referenced (reference 4), which is what sailing displays show. A family not received for longer than the wind TTL (2 s) is ignored: an instrument that stops sending reference 4 but keeps sending reference 3 is read as ground referenced, and with neither family fresh true wind is computed as below (all null when there is no apparent wind). The missing half is filled in from the vessel's true heading, the same headingTrue as in speed_course:
- an instrument that sends an angle (reference 3 or 4) gets
direction = headingTrue + angle - one that sends a direction (reference 0) gets
angle = direction − headingTrue - a magnetic direction (reference 1) is corrected with the magnetic variation (from the heading source, else PGN 127258); without a variation,
directionisnull
Computed. When no wind_true source is selected but a wind source is, true wind is computed from the apparent wind (AWS, AWA), the speed through water (STW, from speed_course) and the true heading, and reference is "water":
x = AWS·cos(AWA) − STW
y = AWS·sin(AWA)
TWS = √(x² + y²)
TWA = atan2(y, x)
TWD = headingTrue + TWA (normalised to 0…360)| Missing | Result |
|---|---|
| apparent speed, apparent angle or speed through water | speed, angle and direction are all null |
| true heading only | speed and angle are published, direction is null |
Speed over ground is deliberately not used in place of a missing speed through water: it would give the wind over the ground, which is a different quantity from the one sailing displays show, under the same name.
wind (deprecated)
wind is the single wind section that predates the split, kept unchanged for one release so the existing screens keep working. It will be removed in the next release; read wind_apparent and wind_true instead.
Its content is what it always was: the selected apparent wind source, or the true wind source when there is no apparent one, with apparentSpeed, apparentAngle, trueSpeed, trueAngle, trueDirection and atmosphericPressure. Its speeds are in m/s, not knots, and its computed true wind uses speed over ground.
Wind damping
Raw apparent wind from a masthead unit is noisy: the vane swings with every gust and every roll of the boat, and a display that follows it exactly is unreadable.
--wind-damping N (or WindDamping) applies an adaptive vector filter, working on the wind's Cartesian components rather than on speed and angle separately, so a shift through north does not produce a spurious excursion. It is adaptive: a small change is smoothed heavily, and a change larger than the filter's threshold is followed quickly, so a real wind shift is not hidden behind the smoothing of the noise.
| Level | Effect |
|---|---|
0 | filter off, raw values published |
1–8 | increasing smoothing |
9 | heaviest smoothing |
The built-in default is 7.
The filter is reset when the wind source changes or after a long gap, so a switch between two masthead units does not drag the old value along.
The navigation source configuration file
Priorities, friendly names and blocks live in a JSON file the daemon reads at startup and re-reads on SIGHUP. Its location depends on how the daemon was started (Configuration); --nav-config PATH overrides it.
json
{
"version": 1,
"windDamping": 7,
"hysteresis": { "position": 1000, "wind": 300 },
"warmup": { "position": 10000, "wind": 5000 },
"sources": [
{
"name": "0x8004008200000001",
"friendlyName": "Masthead GPS",
"dataTypes": [
{ "type": "position", "priority": 1, "blocked": false },
{ "type": "course_speed", "priority": 1, "blocked": false }
]
},
{
"name": "0x8014009000000003",
"friendlyName": "Cockpit wind (faulty)",
"dataTypes": [
{ "type": "wind", "priority": 50, "blocked": true }
]
}
]
}| Key | Meaning |
|---|---|
version | file format version |
windDamping | 0–9, same meaning as the flag |
hysteresis | per-data-type override in milliseconds |
warmup | per-data-type override in milliseconds |
sources[].name | the device's J1939 NAME as a hex string |
sources[].friendlyName | up to 64 characters, shown in reports and tools |
sources[].dataTypes[].type | position, heading, course_speed, wind, wind_true, depth_water, autopilot, date, motor |
sources[].dataTypes[].priority | 1–99, 1 highest; out-of-range values are clamped |
sources[].dataTypes[].instance | engine instance, motor type only |
sources[].dataTypes[].blocked | true removes the source from selection for that type |
speed_course is accepted as a synonym of course_speed when reading the file. wind is the apparent wind; wind_true has no hysteresis or warmup key of its own and uses the wind values. A source that has a wind entry and no wind_true entry (a file written before the two were split) gets the same priority and block for wind_true, so a masthead unit blocked for wind stays blocked for both. At most 256 sources are kept, and the file is refused above 1 MB.
The file is written by muxen-nmea2000-config, not by hand. Editing it by hand and sending SIGHUP works, but the tool is the supported path because it can see what is actually on the bus.
muxen-nmea2000-config
A full-screen terminal tool that lists every device the daemon has discovered, with its manufacturer, product information, the data types it supplies and the priority in force, and lets you change them.
It does not touch the CAN bus. It talks to the running daemon over a Unix socket and asks it what is on the bus. That is deliberate: a second program doing its own address claim on the same bus would be one more device to arbitrate with, and it would see a different picture from the daemon whose decisions you are trying to change.
sh
muxen-nmea2000-config # the primary daemon
muxen-nmea2000-config -I can0 # a templated instance
muxen-nmea2000-config -r # read-only, safe on a live boat| Option | Effect |
|---|---|
-s, --socket PATH | connect to a specific socket |
-I, --instance NAME | connect to /run/muxen-nmea2000-<NAME>/sources.sock |
-r, --read-only | monitor without editing |
-h, --help | usage, then exit |
-V, --version | version, then exit |
Keys:
| Key | Action |
|---|---|
↑ ↓, k j | move between sources and data types |
← → | expand or collapse a source |
PgUp PgDn | jump device |
+ - | raise or lower the priority of the selected data type |
0–9 | on a device row, jump to the nth device; on a data-type row, set the priority or engine instance directly |
B | block or unblock the selected data type |
N | edit the friendly name |
I | change a remote device's instance, over PGN 126208 |
a, A | show or hide devices that supply no navigation data |
S | save the configuration |
R | tell the daemon to reload it |
Q | quit |
F1 | help |
A device that sends PGN 130306 is listed under both wind and wind_true: the reference is carried by each message, not by the PGN, so the tool cannot tell in advance which winds a device will send. Set the priority on the row that matters; the other has no effect until the device sends that kind of wind.
The tool polls the daemon every 2 seconds, so a device that appears on the bus shows up within a couple of seconds without any action.
S writes the file through the daemon, and R makes the daemon reload it. They are separate on purpose: you can stage a set of changes and apply them in one step.
Changing a remote device's instance
I is the one key that changes something outside the Brain. NMEA 2000 devices carry an instance number that distinguishes, for example, port engine from starboard engine, and two devices shipped with the same instance will collide on the screens. The daemon can rewrite it over the bus, using the standard Command Group Function (PGN 126208), without any manufacturer tooling.
The device must accept the command; some refuse, and the tool reports the refusal. Change one device at a time and confirm the result in the source list before moving on.
The IPC socket
The tool and the daemon speak newline-delimited JSON over a Unix stream socket. It is documented here because it is a stable interface other tooling can use, not because it needs to be driven by hand.
- Default path
/run/muxen-nmea2000/sources.sock, overridable with the daemon's--ipc-socket. - The daemon prefers systemd's
RUNTIME_DIRECTORYwhen set, which is what makes an instance's socket land under/run/muxen-nmea2000-<instance>/. - The socket is world read/write (0666); the directory containing it is 0750. Any local user who can traverse the directory can therefore read the source list and change navigation priorities. There is no authentication. Treat shell access to the Brain as full access to this interface.
- The server starts only once all four virtual devices have claimed addresses, so it exists whether or not
--report-navis on. - Up to 8 concurrent clients, 5 s per connection, 64 KiB per request and per response.
Requests are {"cmd": "<verb>", ...}; responses are {"status": "ok", "data": {…}} or {"status": "error", "error": "…"}.
| Verb | Purpose |
|---|---|
ping | liveness; returns pong and uptime |
list_sources | every device in the peer registry, with product info, NAME breakdown, TX/RX PGN lists, instances and data types |
get_nav_config | the current configuration: configPath, configLoaded, sources |
set_config | write a new sources array to the configuration file |
reload_config | re-read the configuration file |
command_instance | change a remote device's instance over PGN 126208 |
command_field | change one field of a remote device's PGN over PGN 126208 |
get_command_status | the outcome of a command_* request, by requestId |
--verbose-ipc logs each connection and command.
Related chapters
- Report modules and how they are turned on — Configuration
- A GPS wired to the Brain instead of the bus — GNSS and gpsd
- Every field, unit and default — Reference
- Which PGNs feed which data type — PGN reference
