Skip to content

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
  • expireAfterSec 2 — 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:

SectionContains
positionfix state, latitude, longitude, altitude, datum, satellite and DOP figures
speed_courseheading and its reference, COG, SOG, speed through water, rate of turn, set and drift
wind_apparentapparent wind speed and angle
wind_truetrue wind speed, angle and direction, and where they came from
winddeprecated, see Wind
depth_waterdepth, transducer offset, water temperature
autopilotsteering 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:

PathUnitNotes
data.position.hasFixbooleanalways present in position
data.position.latitude, .longitudedegreespresent only when hasFix is true
data.speed_course.sogknotsalways present in speed_course, null when no source sends it
data.speed_course.cogdegrees, truealways present in speed_course, null when no source sends it
data.speed_course.speedThroughWaterknotspresent only when a source sends it
data.speed_course.headingTruedegreespresent only when it can be resolved
data.wind_apparent.speedknotsnull when not sent
data.wind_apparent.angledegrees, −180…180relative to the bow, starboard positive; null when not sent
data.wind_true.speedknotsnull when not obtainable
data.wind_true.angledegrees, −180…180relative to the bow, starboard positive; null when not obtainable
data.wind_true.directiondegrees, 0…360relative to true north, where the wind comes from; null when not obtainable
metadata.expireAfterSecseconds2

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:

ConditionMeaning
not blockedthe installer has not disabled it for this data type
has sent dataat least one message of that type has arrived
at least 2 consecutive valid messagesa single stray frame does not promote a source
expiration counter below 5see below
warm-up completesee below

Among the candidates, the rule is:

  1. 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.
  2. 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 typeTTLHysteresisWarm-up
position5 s1000 ms10000 ms (after fix)
heading5 s500 ms10000 ms
course_speed5 s500 ms10000 ms
wind (apparent)2 s300 ms5000 ms
wind_true2 ssame as windsame as wind
depth_water5 s500 ms10000 ms
autopilot5 s500 ms5000 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:

ReferenceMeaningData type
2apparent, relative to the bowwind
0true, relative to true north (a direction)wind_true, ground
1true, relative to magnetic north (a direction)wind_true, ground
3true, relative to the bow, over the groundwind_true, ground
4true, relative to the bow, through the waterwind_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.

FieldUnitMeaning
speedknotsapparent wind speed
angledegrees, −180…180apparent wind angle, relative to the bow, starboard positive
sourceas for every section, with --report-metadata-source

wind_true ​

FieldUnitMeaning
speedknotstrue wind speed
angledegrees, −180…180true wind angle, relative to the bow, starboard positive
directiondegrees, 0…360true wind direction, relative to true north: where the wind comes from
reference"water" or "ground"what the true wind is relative to
computedbooleantrue when the daemon computed it, false when an instrument sent it
sourcewith --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, direction is null

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)
MissingResult
apparent speed, apparent angle or speed through waterspeed, angle and direction are all null
true heading onlyspeed 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.

LevelEffect
0filter off, raw values published
1–8increasing smoothing
9heaviest 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 }
      ]
    }
  ]
}
KeyMeaning
versionfile format version
windDamping0–9, same meaning as the flag
hysteresisper-data-type override in milliseconds
warmupper-data-type override in milliseconds
sources[].namethe device's J1939 NAME as a hex string
sources[].friendlyNameup to 64 characters, shown in reports and tools
sources[].dataTypes[].typeposition, heading, course_speed, wind, wind_true, depth_water, autopilot, date, motor
sources[].dataTypes[].priority1–99, 1 highest; out-of-range values are clamped
sources[].dataTypes[].instanceengine instance, motor type only
sources[].dataTypes[].blockedtrue 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
OptionEffect
-s, --socket PATHconnect to a specific socket
-I, --instance NAMEconnect to /run/muxen-nmea2000-<NAME>/sources.sock
-r, --read-onlymonitor without editing
-h, --helpusage, then exit
-V, --versionversion, then exit

Keys:

KeyAction
↑ ↓, k jmove between sources and data types
← →expand or collapse a source
PgUp PgDnjump device
+ -raise or lower the priority of the selected data type
0–9on a device row, jump to the nth device; on a data-type row, set the priority or engine instance directly
Bblock or unblock the selected data type
Nedit the friendly name
Ichange a remote device's instance, over PGN 126208
a, Ashow or hide devices that supply no navigation data
Ssave the configuration
Rtell the daemon to reload it
Qquit
F1help

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_DIRECTORY when 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-nav is 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": "…"}.

VerbPurpose
pingliveness; returns pong and uptime
list_sourcesevery device in the peer registry, with product info, NAME breakdown, TX/RX PGN lists, instances and data types
get_nav_configthe current configuration: configPath, configLoaded, sources
set_configwrite a new sources array to the configuration file
reload_configre-read the configuration file
command_instancechange a remote device's instance over PGN 126208
command_fieldchange one field of a remote device's PGN over PGN 126208
get_command_statusthe outcome of a command_* request, by requestId

--verbose-ipc logs each connection and command.

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