AI-generated text still pending human review/editing.
ISO-TP transport ↗

NMEA 2000

NMEA 2000 (IEC 61162-3) is the network marine electronics speak: chartplotters, GPS receivers, wind and depth sensors, engine gateways, battery monitors. Under the hood it is J1939 with a marine dictionary, the same 29-bit CAN framing, the same PGN/source-address structure, the same ISO address claim. Three things differ: the message catalogue, the multi-frame transport (Fast Packet), and how fields are packed.

It's J1939 at the wire level

Every NMEA 2000 frame is a standard CAN 2.0B frame with a 29-bit extended identifier, and the identifier is split exactly the way J1939 does it:

BitsFieldMeaning
28–26Priority0 = highest
25–24Reserved / Data Pageextends the PGN range
23–16PDU Format (PF)≥ 0xF0 → broadcast (PGN includes byte 15–8)
15–8PDU Specific (PS)destination addr (PF<0xF0) or PGN low byte
7–0Source Addresswhich device sent it

The PGN (Parameter Group Number) identifies what the message is, e.g. 127250 Vessel Heading, 130306 Wind Data. sloppyCAN reuses its J1939 ID parser verbatim and just swaps in a marine PGN dictionary when you pick NMEA 2000 from the protocol dropdown in the J1939 / N2K tab.

The bus runs at 250 kbit/s. Set the header Speed dropdown to 250k before connecting to a real backbone. sloppyCAN does not change it for you.

Fast Packet

A single CAN frame carries at most 8 data bytes. J1939 moves longer messages with its Transport Protocol (TP.CM / TP.DT); NMEA 2000 does not. It uses Fast Packet, which packs the sequencing into the data field of ordinary frames of the same PGN. Whether a PGN is single-frame or Fast Packet is a fixed property of that PGN (it's in the dictionary).

Byte 0 of every Fast Packet frame is a control byte: the top 3 bits are a sequence counter (0–7, ties one logical message together) and the low 5 bits are a frame counter (the ordinal within the message).

First frame (frame counter = 0)
SC|0byte 0
LENbyte 1
D0byte 2
D1
D2
D3
D4
D5byte 7

Byte 1 is the total payload length; bytes 2–7 carry the first 6 payload bytes.

Continuation frames (frame counter = 1, 2, 3 …)
SC|nbyte 0
Dbyte 1
D
D
D
D
D
Dbyte 7

Each continuation carries 7 payload bytes, placed at offset 6 + (frameCounter − 1) × 7. Collect frames until you have LEN bytes, then decode the whole buffer as that PGN. A Fast Packet message can be up to 223 bytes.

sloppyCAN keys each reassembly slot by (PGN, source, sequence) and drops a slot that makes no progress for ~1 s, so one lost middle frame can't wedge it. In the PGN Monitor and Frame Log, reassembled messages get an FP badge.

Field encoding

NMEA 2000 fields are little-endian and frequently bit-packed: a field can be 2, 4 or 6 bits wide and start anywhere, not just on a byte boundary. (J1939's whole-byte SPN model can't express this, so the marine decoder works in bit offsets.) Each field has a resolution, an optional offset, and a unit; the all-ones value means data not available and the next value down means out of range.

QuantityResolutionNote
Angle (heading, wind, COG)0.0001 radshown in degrees
Temperature0.01 Kshown in °C (−273.15)
Pressure100 Pashown in kPa / hPa
Speed0.01 m/s
Latitude / Longitude1e-7 deg32-bit signed (rapid)

Worked Example: 130306 Wind Data

Wind Data is a single-frame PGN. The 8 data bytes lay out as:

SIDbyte 0
SPDbyte 1–2
SPD
ANGbyte 3–4
ANG
REFbyte 5
-byte 6–7
-

Payload: FF 4E 06 60 16 02 FF FF

  • Wind Speed = bytes 1–2 LE = 0x064E = 1614 × 0.01 m/s = 16.14 m/s
  • Wind Angle = bytes 3–4 LE = 0x1660 = 5728 × 0.0001 rad = 0.5728 rad = 32.8°
  • Reference = byte 5 low bits = 2 = Apparent

That's exactly what the PGN Monitor shows when you run Demo with the dropdown set to NMEA 2000. It streams heading, engine RPM, position, wind, and a Fast-Packet engine-dynamic message so you can watch reassembly happen with no hardware attached.

Worked Example: 127245 Rudder

Rudder is a single-frame PGN. sloppyCAN's own boat only ever has an applied position to report, never a distinct commanded one, so the Direction Order and Angle Order fields stay not available and only Position gets written:

Instbyte 0
Dirbyte 1
Ordbyte 2–3
Ord
POSbyte 4–5
POS
-byte 6–7
-

Payload: FF FF FF FF AF F1 FF FF

  • Position = bytes 4–5 LE = 0xF1AF = a signed 16-bit value, so subtract 65536: 61871 − 65536 = −3665 × 0.0001 rad = −0.3665 rad = −21.0° (to port)

That −21° is exactly the contract's rudder_actual (−60%) converted through nmea2000.js's own hard-over constant — 35° of full throw — the one percentage-to-degrees conversion this file declares, because PGN 127245 carries a real angle and the contract's rudder signals are percentages.

Worked Example: 127237 Heading/Track Control

A 21-byte Fast Packet message (so it arrives as three CAN frames — the shape from the Fast Packet section above). sloppyCAN's boat only ever fills two of its many fields: the Steering Mode bits (which of the pilot's own two states it is in) and the Heading-To-Steer, both of which double as the closest legal expression of the contract's nav_mode_actual / heading_target. Every other field — the four limit-exceeded flags, Turn Mode, both rudder-order fields, the track and cross-track limits — stays not available, because this boat runs heading-hold alone and has no route, no cross-track error, and no distinct rudder order to publish.

Reassembled payload (byte 0 and bytes 2–4, 7–20 all 0xFF):

  • byte 1, bits 0–2 = 0xFB → low 3 bits 011 = 3, Heading Control Standalone — the pilot is actively holding a course
  • bytes 5–6 LE = 0x14 0xB8 = 0xB814 = 47124 × 0.0001 rad = 4.7124 rad = 270.0°, the Heading-To-Steer

In STANDBY the same two fields read 0 (Heading Control off) and the boat's current heading — never a sentinel, because the contract wants a bus that engages the pilot with no course of its own to capture whatever heading the boat is already on rather than steer to an arbitrary bearing.

Worked Example: 130577 Direction Data

A 14-byte Fast Packet message. Like the Heading/Track Control message above, most of its fields (Data Mode, COG Reference, SID, COG, SOG, Heading, Speed Through Water) stay not available — each already has its own dedicated PGN above with its own source address, so repeating them here would be the same value published twice from two different "instruments." Only Set and Drift, the tidal-current pair with no PGN of their own elsewhere, get written:

Reassembled payload (bytes 0–9 all 0xFF):

  • bytes 10–11 LE = 0x5B 0x88 = 0x885B = 34907 × 0.0001 rad = 3.4907 rad = 200.0°, the Set (bearing the current flows toward)
  • bytes 12–13 LE = 0x28 0x00 = 40 × 0.01 m/s = 0.40 m/s, the Drift

What makes a marine dash different from a truck's

The same physical quantity has two names on a boat that a land vehicle never has to tell apart, because a boat's medium moves under it:

PairThrough/apparentOver/true
Speed STW (128259) — through the WATER, what a paddlewheel log reads SOG (129026) — over the GROUND; the gap is the tide's own push or drag
Wind AWA / AWS (130306, apparent) — what the hull actually feels, bow-referenced TWD / TWS (130306, true) — the wind's real bearing and speed, with the hull's own motion subtracted back out
Course Heading (127250) — where the bow POINTS COG (129026) — the course actually MADE; the gap is leeway plus the set of the tide, exactly what 130577's Set/Drift describes

Depth has its own version of the same trap: 128267 reports water under the transducer, which rides wherever the hull floats it — not under the keel, and not to the sea bed from the surface. A shoaling reading and a squatting hull both move that number the same direction, and the PGN has no way to tell you which.

The one boat reading with no PGN at all is the sail: the contract's sheet (how far the boom may swing) and sail_angle (where the wind put it) ride the base CAN map and the uplink respectively, because NMEA 2000 defines no sail PGN — not an oversight in this project, but what the standard itself is. A real marine bus carries what its committee thought worth standardising, and sail trim was never that.