Appearance
AIS
AIS is how ships announce themselves. Every vessel above a certain size, and a great many below it, carries a transponder that broadcasts its identity, position, course and speed on VHF. An AIS receiver on the boat picks those broadcasts up and puts them on the NMEA 2000 bus, and that is what draws the other traffic on the chart.
muxen-nmea2000 turns that stream into a single picture of everything currently around the boat: vessels, navigation marks that transmit their own position, search-and-rescue aircraft, and safety broadcasts. One message, rebuilt every 30 seconds, with each target's own position and recent track.
Why it needs assembling
An AIS transponder does not send one message describing a ship. It sends several different ones, on different schedules, and each carries a fraction of the picture:
- Position every 2–10 seconds for a Class A vessel under way, every 30 seconds for Class B.
- Identity and voyage — the vessel's name, call sign, type, dimensions, destination and draught — roughly every 6 minutes, and split across two separate messages for Class B.
So a ship appears on the chart within seconds as a moving triangle with no name, and acquires its name minutes later. That is the protocol working correctly, not a fault.
The only thing tying those messages together is the MMSI, a 9-digit number unique to the transponder. muxen-nmea2000 keys everything on the MMSI, merges each new message into the target it already has, and ages the parts out separately — because a name learned six minutes ago is still true, while a position from six minutes ago is not.
The MMSI also says what kind of thing is transmitting, from its leading digits: a number starting 99 is a navigation mark, one starting 111 is a search-and-rescue aircraft, and the rest are vessels. That is how targets are sorted into the three groups below.
What gets published
Enable it with --report-ais, or ReportAis in the deploy configuration.
- Topic
nmea/ais - Rate every 30 seconds
- QoS 0, retained
expireAfterSec30
data
├── ownShip ← this boat, from the navigation report
├── aisTargets ← object keyed by MMSI
├── atons ← object keyed by MMSI
├── sarAircraft ← object keyed by MMSI
└── safetyMessages ← array
metadataownShip is present only when the navigation report has a position for this boat. It is what lets a consumer draw the traffic relative to the vessel without subscribing to a second topic.
A target carries, depending on what has arrived:
| Group | Fields |
|---|---|
| top level | mmsi, aisClass, lat, lon, rxTs, source |
dynamics | course over ground, speed over ground, heading, rate of turn |
identity | vessel name, call sign, IMO number, ship type |
dimensions | length, beam |
voyage | destination, ETA, draught, navigation status |
track | recent positions, newest last |
| closest approach | cpaNm, tcpaSec |
Aids to navigation carry mmsi, name, position, atonType, offPosition and virtualAton — the last distinguishing a real buoy with a transponder from a mark that exists only as a broadcast.
Search-and-rescue aircraft carry mmsi, position and alt.
Safety messages carry sourceMmsi, text, rxTs and, when addressed rather than broadcast, destinationMmsi.
The authoritative description of the payload is the JSON Schema at schemas/ais-schema.json, with a worked example in schemas/ais-sample.json. Where this chapter and the schema disagree, the schema is right.
Ageing
Each kind of information has its own time to live, counted from when it last arrived:
| Information | TTL |
|---|---|
| position (Class A and Class B) | 60 s |
| identity, voyage and other static data | 300 s |
| aid to navigation | 180 s |
| SAR aircraft | 60 s |
| safety message | 300 s |
A target whose position has expired stops being reported. Static data outliving position by five minutes is deliberate: a vessel that drops out of VHF range briefly comes back with its name intact rather than appearing as a fresh unnamed contact.
Track history
--ais-track-depth N (or AisTrackDepth) sets how many past positions are kept per target, so a consumer can draw a trail. The default is 10 and the value is clamped to 0–100; 0 disables track history.
Deeper tracks make the payload larger for every target at once. On a busy waterway with a hundred targets the difference between depth 10 and depth 100 is an order of magnitude in message size, republished every 30 seconds.
Which PGNs feed it
| PGN | Name |
|---|---|
| 129038 | AIS Class A Position Report |
| 129039 | AIS Class B Position Report |
| 129040 | AIS Class B Extended Position Report |
| 129041 | AIS Aids to Navigation Report |
| 129793 | AIS UTC and Date Report |
| 129794 | AIS Class A Static and Voyage Related Data |
| 129795 | AIS Addressed Safety Related Message |
| 129796 | AIS Acknowledge |
| 129797 | AIS Binary Broadcast Message |
| 129798 | AIS SAR Aircraft Position Report |
| 129801 | AIS Addressed Safety Related Message |
| 129802 | AIS Safety Related Broadcast Message |
| 129809 | AIS Class B Static Data, Part A |
| 129810 | AIS Class B Static Data, Part B |
| 129812 | AIS Single Slot Binary Message |
All are Fast Packet except 129793.
Class B static data arriving in two parts — 129809 carrying the name, 129810 the call sign, type and dimensions — is why a Class B vessel can show a name for some minutes before the rest of its details fill in.
What it does not do
- It does not transmit AIS. This daemon never puts an AIS message on the bus. Transmitting requires a licensed transponder with the boat's own MMSI.
- It does not decide anything is dangerous.
cpaNmandtcpaSecare computed and published; deciding what to do about them belongs to whatever draws the chart. There is no collision alarm here. - It is not a receiver. No AIS receiver on the bus means no targets, and the daemon has no way to tell that apart from empty water.
Diagnostics
sh
mosquitto_sub -h 127.0.0.1 -t 'nmea/ais' -v--verbose-ais logs each target as it is created, merged and expired, including how many targets were removed on each pass.
Empty water on a busy day is nearly always one of three things: the module is off, the receiver is not on the bus, or the antenna is disconnected. nmea/devices settles the second, and nmea/device/<addr>/pgn/129038 with --mqtt-publish-pgn settles the third. See Troubleshooting.
