Native javascript version of canboat
npm install @canboat/canboatjs



A TypeScript library for parsing, encoding, and interfacing with NMEA 2000 marine electronics networks. It used the PGN definition database from canboat with extensive device support, multiple data format compatibility, and PGN output.
- Features
- Architecture
- Installation
- Quick Start
- Supported Devices
- Command Line Tools
- API Reference
- Usage Examples
- Device Integration
- Data Formats
- Contributing
- License
- 🔌 Multi-Device Support: Direct interface with popular NMEA 2000 gateways and CAN bus devices
- 📡 Multiple Data Formats: Parse and generate various N2K data formats (Actisense, iKonvert, YDWG, etc.)
- 🔄 Bidirectional: Both decode incoming N2K messages and encode/transmit outgoing messages
- 🛠️ Command Line Tools: Ready-to-use CLI utilities for data conversion and analysis
- 🎯 Type Safety: Built with TypeScript and includes type definitions
- 📊 JSON Output: Standardized JSON format compatible with Signal K and other marine data systems
For an overview of how canboatjs integrates with signalk-server to create a complete marine data processing pipeline, see the Architecture Diagram.
The diagram illustrates:
- Data Flow: From NMEA 2000 hardware through parsing and conversion to Signal K format
- Device Support: Various hardware interfaces and data formats
- Bidirectional Processing: Both incoming data parsing and outgoing data generation
- Integration Points: How canboatjs works with other components in the marine data ecosystem
``bash`
sudo npm install -g @canboat/canboatjs
`bash`
npm install @canboat/canboatjs
- Node.js: Version 20 or higher
- Optional Dependencies:
- serialport: For serial device communicationsocketcan
- : For direct CAN bus access on Linux
`javascript
const { FromPgn } = require('@canboat/canboatjs')
// Create parser instance
const parser = new FromPgn()
// Handle warnings
parser.on('warning', (pgn, warning) => {
console.log([WARNING] PGN ${pgn.pgn}: ${warning})
})
// Parse an Actisense format message
const message = "2017-03-13T01:00:00.146Z,2,127245,204,255,8,fc,f8,ff,7f,ff,7f,ff,ff"
const json = parser.parseString(message)
if (json) {
console.log('Parsed PGN:', JSON.stringify(json, null, 2))
}
`
`javascript
const { pgnToActisenseSerialFormat } = require('@canboat/canboatjs')
// Create a rudder position message
const message = {
pgn: 127245,
prio: 2,
src: 204,
dst: 255,
fields: {
'Instance': 252,
'Direction Order': 0,
'Reserved1': '62'
}
}
const actisenseString = pgnToActisenseSerialFormat(message)
console.log('Generated:', actisenseString)
`
Canboatjs includes several powerful command-line utilities:
`bashFrom Actisense NGT-1
actisense-serialjs /dev/ttyUSB0 | analyzerjs
$3
Convert JSON to various N2K formats:`bash
echo '{"pgn":127245,"fields":{"Instance":0}}' | to-pgn --format=actisense
`$3
Read from SocketCAN without installing can-utils:`bash
Basic CAN bus monitoring
candumpjs can0Filter by specific PGN numbers
candumpjs can0 --pgn 129025 # Position updates only
candumpjs can0 --pgn 127245 # Rudder data onlyFilter by multiple PGNs
candumpjs can0 --pgn 129025 --pgn 127245 --pgn 129029Filter by source address (device that sent the message)
candumpjs can0 --src 15 # From device address 15
candumpjs can0 --src 127 # From device address 127Filter by destination address
candumpjs can0 --dst 255 # Broadcast messages only
candumpjs can0 --dst 204 # Messages to device 204Filter by manufacturer
candumpjs can0 --manufacturer "Garmin"Combine multiple filters
candumpjs can0 --pgn 129025 --src 15 --dst 255Output in Actisense format instead of JSON
candumpjs can0 --format actisensePretty print JSON with filtering
candumpjs can0 --pgn 129025 --prettyCommon PGN filters for navigation data
candumpjs can0 --pgn 129025 --pgn 129026 --pgn 129029 # GPS position data
candumpjs can0 --pgn 127245 --pgn 127250 # Rudder and heading
candumpjs can0 --pgn 128267 --pgn 128259 # Depth and speed
`$3
Process Yacht Devices recorder files:`bash
ydvr-file recording.ydvr | analyzerjs
`$3
- actisense-file - Process Actisense log files
- actisense-n2k-tcp - TCP server for Actisense data
- cansendjs - Send CAN messages
- ikonvert-serial - iKonvert serial interface$3
Both
analyzerjs and candumpjs support powerful filtering options to focus on specific data:#### PGN Filtering
Filter by Parameter Group Number to see only specific message types:
`bash
Navigation data
--pgn 129025 # Position, Rapid Update
--pgn 129026 # COG & SOG, Rapid Update
--pgn 129029 # GNSS Position Data
--pgn 127250 # Vessel HeadingEngine data
--pgn 127488 # Engine Parameters, Rapid Update
--pgn 127489 # Engine Parameters, DynamicEnvironmental data
--pgn 128267 # Water Depth
--pgn 128259 # Speed
--pgn 130311 # Environmental ParametersMultiple PGNs
--pgn 129025 --pgn 129026 --pgn 127250
`#### Source and Destination Filtering
Filter by device addresses to monitor specific devices or message types:
`bash
Source address filtering (device that sent the message)
--src 15 # Messages from device address 15 (often a chartplotter)
--src 127 # Messages from device address 127 (often a GPS)
--src 204 # Messages from device address 204 (often an autopilot)Destination address filtering
--dst 255 # Broadcast messages (most common)
--dst 204 # Directed messages to device 204
--dst 15 # Directed messages to device 15Multiple source/destination addresses
--src 15 --src 127 --dst 255
`#### Manufacturer Filtering
Filter by device manufacturer:
`bash
--manufacturer "Garmin"
--manufacturer "Raymarine"
--manufacturer "Simrad"
--manufacturer "Furuno"
`#### Practical Examples
`bash
Monitor only GPS position data from all devices
candumpjs can0 --pgn 129025 --pgn 129029Watch rudder and autopilot commands
actisense-serialjs /dev/ttyUSB0 | analyzerjs --pgn 127245 --pgn 127237Filter Garmin devices only
nc chartplotter-ip 1475 | analyzerjs --manufacturer "Garmin"Monitor messages from chartplotter (address 15) to autopilot (address 204)
candumpjs can0 --src 15 --dst 204Watch GPS data from specific device
actisense-serialjs /dev/ttyUSB0 | analyzerjs --pgn 129025 --src 127Monitor all broadcast navigation messages
candumpjs can0 --pgn 129025 --pgn 129026 --pgn 127250 --dst 255Combine filters for specific Garmin GPS data
candumpjs can0 --pgn 129025 --manufacturer "Garmin" --prettyDebug communication between specific devices
analyzerjs --file network_log.txt --src 15 --dst 204 --pretty
`#### Common PGN Reference
| PGN | Description | Use Case |
|-----|-------------|----------|
| 129025 | Position, Rapid Update | GPS lat/lon monitoring |
| 129026 | COG & SOG, Rapid Update | Course and speed tracking |
| 129029 | GNSS Position Data | Detailed GPS information |
| 127250 | Vessel Heading | Compass/heading data |
| 127245 | Rudder | Steering position |
| 127237 | Heading/Track Control | Autopilot commands |
| 128267 | Water Depth | Depth sounder data |
| 128259 | Speed | Speed through water |
| 127488 | Engine Parameters, Rapid | RPM, temperature |
| 130311 | Environmental Parameters | Air/water temperature |
#### Understanding Source and Destination
NMEA 2000 messages include source (src) and destination (dst) address fields:
- Source (src): Address of the device sending the message (0-251)
- Destination (dst): Target device address, or 255 for broadcast
Both
analyzerjs and candumpjs support filtering by these address fields using --src and --dst options. Common device addresses:- 15: Often a chartplotter/display
- 127: Often a GPS receiver
- 204: Often an autopilot
- 255: Broadcast to all devices (most common for dst)
Examples:
`bash
Filter messages from GPS receiver
analyzerjs --src 127Filter only broadcast messages
candumpjs can0 --dst 255Monitor autopilot commands (directed messages to address 204)
analyzerjs --dst 204 --pgn 127237
`API Reference
$3
####
FromPgn - Message Parser
`javascript
const parser = new FromPgn(options)
parser.parseString(message) // Parse single message
parser.parse(buffer) // Parse binary data
`####
canbus - CAN Bus Interface
`javascript
const canbus = new canbus(options)
canbus.sendPGN(message) // Send N2K message
`#### Device-Specific Streams
-
Ydwg02 - Yacht Devices YDWG-02 interface
- W2k01 - Actisense W2K-1 interface
- iKonvert - Digital Yacht iKonvert interface
- Venus - Victron Venus OS interface
- serial - Actisense NGT-1 serial interface$3
`javascript
const {
parseN2kString, // Parse N2K string formats
isN2KString, // Detect N2K string format
toActisenseSerialFormat, // Convert to Actisense format
pgnToActisenseSerialFormat,
pgnToiKonvertSerialFormat,
pgnToYdgwRawFormat,
lookupEnumerationValue, // Get enumeration values
lookupEnumerationName, // Get enumeration names
discover // Network device discovery
} = require('@canboat/canboatjs')
`Usage Examples
$3
`javascript
const { FromPgn, serial } = require('@canboat/canboatjs')// Connect to Actisense NGT-1
const actisense = new serial({
device: '/dev/ttyUSB0',
baudrate: 115200
})
const parser = new FromPgn()
actisense.pipe(parser)
parser.on('pgn', (pgn) => {
if (pgn.pgn === 129025) { // Position Rapid Update
console.log(
Lat: ${pgn.fields.Latitude}, Lon: ${pgn.fields.Longitude})
}
})
`$3
`javascript
const { discover } = require('@canboat/canboatjs')// Discover NMEA 2000 devices on network
discover((device) => {
console.log(
Found device: ${device.name} at ${device.address}:${device.port})
})
`$3
`javascript
const { FromPgn } = require('@canboat/canboatjs')
const parser = new FromPgn()// Actisense format
const actisense = "2017-03-13T01:00:00.146Z,2,127245,204,255,8,fc,f8,ff,7f,ff,7f,ff,ff"
// YDWG-02 format
const ydwg = "16:29:27.082 R 09F8017F 50 C3 B8 13 47 D8 2B C6"
// MiniPlex-3 format
const miniplex = "$MXPGN,01F801,2801,C1308AC40C5DE343*19"
// All parse to same JSON structure
[actisense, ydwg, miniplex].forEach(message => {
const json = parser.parseString(message)
if (json) {
console.log(
PGN ${json.pgn}: ${json.description})
}
})
`$3
`javascript
const { SimpleCan } = require('@canboat/canboatjs')// Create virtual N2K device
const device = new SimpleCan({
canDevice: 'can0',
preferredAddress: 35,
addressClaim: {
"Unique Number": 139725,
"Manufacturer Code": 'My Company',
"Device Function": 130,
"Device Class": 'Navigation',
"Device Instance Lower": 0,
"Device Instance Upper": 0,
"System Instance": 0,
"Industry Group": 'Marine'
},
productInfo: {
"NMEA 2000 Version": 1300,
"Product Code": 667,
"Model ID": "MyDevice-1000",
"Software Version Code": "1.0",
"Model Version": "1.0",
"Model Serial Code": "123456"
}
}, (message) => {
// Handle incoming messages
console.log('Received:', message)
})
device.start()
`Data Formats
$3
| Format | Description | Example |
|--------|-------------|---------|
| Actisense | Standard timestamped CSV |
2017-03-13T01:00:00.146Z,2,127245,204,255,8,fc,f8,ff,7f,ff,7f,ff,ff |
| Actisense N2K ASCII | Actisense ASCII format | A764027.880 CCF52 1F10D FC10FF7FFF7FFFFF |
| YDWG Raw | Yacht Devices binary | 16:29:27.082 R 09F8017F 50 C3 B8 13 47 D8 2B C6 |
| iKonvert | Digital Yacht base64 | !PDGY,127245,255,/Pj/f/9///8= |
| PCDIN | Chetco Digital | $PCDIN,01F119,00000000,0F,2AAF00D1067414FF*59 |
| MXPGN | MiniPlex-3 | $MXPGN,01F801,2801,C1308AC40C5DE343*19 |
| candump1 | Linux CAN utils (Angstrom) | <0x18eeff01> [8] 05 a0 be 1c 00 a0 a0 c0 |
| candump2 | Linux CAN utils (Debian) | can0 09F8027F [8] 00 FC FF FF 00 00 FF FF |
| candump3 | Linux CAN utils (log) | (1502979132.106111) slcan0 09F50374#000A00FFFF00FFFF |$3
Generate data in any supported format from JSON:
`javascript
const message = { pgn: 127245, fields: { Instance: 0 } }// Convert to different formats
const actisense = pgnToActisenseSerialFormat(message)
const ikonvert = pgnToiKonvertSerialFormat(message)
const ydwg = pgnToYdgwRawFormat(message)
const pcdin = pgnToPCDIN(message)
const mxpgn = pgnToMXPGN(message)
`Building from Source
`bash
Clone repository
git clone https://github.com/canboat/canboatjs.git
cd canboatjsInstall dependencies
npm installBuild TypeScript
npm run buildRun tests
npm testRun with coverage
npm run code-coverageLint and format
npm run format
`Testing
`bash
Run all tests
npm testRun with file watching
npm run dev-testGenerate coverage report
npm run code-coverage
`PGN Definitions
The PGN (Parameter Group Number) definitions used by canboatjs come from the canboat project via canboat.json.
$3
To add or update PGN definitions:
1. Submit changes to canboat: Modify pgn.h in the upstream canboat project
2. Include sample data: Provide real-world message examples
3. Create issue here: Let us know about the changes so we can update canboatjs
This ensures consistency across the entire canboat ecosystem.
Contributing
We welcome contributions! Please see our Contributing Guidelines for details.
$3
1. Fork the repository
2. Create a feature branch:
git checkout -b feature/amazing-feature
3. Make your changes with tests
4. Run the test suite: npm test
5. Run formating and linting: npm run format
6. Commit following Angular conventions
7. Submit a Pull Request$3
Use the format:
- feat: New feature
- fix: Bug fix
- docs: Documentation changes
- style: Code formatting
- refactor: Code restructuring
- test: Adding tests
- chore: Maintenance tasks
License
Licensed under the Apache License, Version 2.0. See LICENSE.md for details.
Related Projects
- @canboat/ts-pgns - TypeScript PGN definitions
- canboat - Original C implementation
- Signal K - Modern marine data standard
- Signal K Server - Signal K server implementation
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Signal K Community: Community
---
Made with ⚓ by the Canboat project contributors
ydvr-file
This program takes input in the YDVR file format and outputs canboat json format
Example:
ydvr-file Usage
Instalation for command line programs
-
sudo npm install -g @canboat/canboatjsInstallation for a nodejs project
-
npm install @canboat/canboatjsCreate the parser
`js
const FromPgn = require("@canboat/canboatjs").FromPgn;const parser = new FromPgn();
parser.on("warning", (pgn, warning) => {
console.log(
[warning] ${pgn.pgn} ${warning});
});
`Parse input from the Actisense NGT-1 or iKonvert string formats
`js
const json = parser.parseString(
"2017-03-13T01:00:00.146Z,2,127245,204,255,8,fc,f8,ff,7f,ff,7f,ff,ff"
);
if (json) {
console.log(JSON.stringify(json));
}
`Output:
`json
{
"description": "Rudder",
"dst": 255,
"prio": 2,
"pgn": 127245,
"fields": {
"Reserved1": "62",
"Direction Order": 0,
"Instance": 252
},
"src": 204,
"timestamp": "2017-03-13T01:00:00.146Z"
}
`Parse input from the YDWG-02
`js
const json = parser.parseString(
"16:29:27.082 R 09F8017F 50 C3 B8 13 47 D8 2B C6"
);
if (json) {
console.log(JSON.stringify(json));
}
`Output:
`json
{
"src": 127,
"pgn": 129025,
"description": "Position, Rapid Update",
"timestamp": "2019-04-10T20:29:27.082Z",
"dst": 255,
"prio": 2,
"fields": {
"Latitude": 33.0875728,
"Longitude": -97.0205113
}
}
`Parse input from the MiniPlex-3-N2K
`js
const json = parser.parseString("$MXPGN,01F801,2801,C1308AC40C5DE343*19");
if (json) {
console.log(JSON.stringify(json));
}
`Output:
`json
{
"src": 1,
"pgn": 129025,
"description": "Position, Rapid Update",
"timestamp": "2019-04-10T20:29:27.082Z",
"dst": 255,
"prio": 0,
"fields": {
"Latitude": 33.0875728,
"Longitude": -97.0205113
}
}
`Generate Actisense format from canboat json
`js
const pgnToActisenseSerialFormat = require("./index")
.pgnToActisenseSerialFormat;const string = pgnToActisenseSerialFormat({
dst: 255,
prio: 2,
pgn: 127245,
fields: {
Reserved1: "62",
"Direction Order": 0,
Instance: 252,
},
src: 204,
});
if (string) {
console.log(string);
}
`Output:
2019-04-10T12:00:32.733Z,2,127245,0,255,8,fc,f8,ff,7f,ff,7f,ff,ffGenerate iKconvert format from canboat json
`js
const pgnToiKonvertSerialFormat = require("./index").pgnToiKonvertSerialFormat;const string = pgnToiKonvertSerialFormat({
dst: 255,
prio: 2,
pgn: 127245,
fields: {
Reserved1: "62",
"Direction Order": 0,
Instance: 252,
},
src: 204,
});
if (string) {
console.log(string);
}
`Output:
!PDGY,127245,255,/Pj/f/9///8=Generate YDGW-02 format from canboat json
`js
const pgnToYdgwRawFormat = require("./index").pgnToYdgwRawFormat;const array = pgnToYdgwRawFormat({
src: 127,
prio: 3,
dst: 255,
pgn: 129029,
fields: {
SID: 0,
Date: "2019.02.17",
Time: "16:29:28",
Latitude: 33.08757283333333,
Longitude: -97.02051133333333,
Altitude: 148.94,
"GNSS type": "GPS+GLONASS",
Method: "GNSS fix",
Integrity: "No integrity checking",
"Number of SVs": 0,
HDOP: 0.5,
PDOP: 1,
"Geoidal Separation": -24,
"Reference Stations": 0,
list: [{ "Reference Station ID": 15 }],
},
});
if (array) {
console.log(JSON.stringify(array, null, 2));
}
`Output:
`json
[
"0df8057f 40 2f 00 18 46 80 d6 62",
"0df8057f 41 23 40 63 1b cc b8 81",
"0df8057f 42 97 04 7f c2 7f fc 96",
"0df8057f 43 23 89 f2 e0 a4 e0 08",
"0df8057f 44 00 00 00 00 12 fc 00",
"0df8057f 45 32 00 64 00 a0 f6 ff",
"0df8057f 46 ff 00 ff 00 ff ff ff"
]
`Parse a N2K string into canId parts and create Buffer
Before the conversion of the individual fields happens the string needs to be parsed for attributes like priority, pgn, destination, source (collectively the CanId) and the hex or base64 needs to be converted to a Buffer. Use
parseN2kString for this purpose.`javascript
const { parseN2kString } = require("@canboat/canboatjs");const n2kParts1 = parseN2kString(
"$PCDIN,01F119,00000000,0F,2AAF00D1067414FF*59"
);
const matches1 =
n2kParts1 ===
{
data: Buffer.from("2AAF00D1067414FF", "hex"),
dst: 255,
format: "PCDIN",
prefix: "$PCDIN",
pgn: 127257,
prio: 0,
src: 15,
timer: 0,
timestamp: new Date(0),
};
const n2kParts2 = parseN2kString(
"16:29:27.082 R 09F8017F 50 C3 B8 13 47 D8 2B C6"
);
const today = new Date().toISOString().split("T")[0];
const matches2 =
n2kParts2 ===
{
canId: 0x09f8017f,
data: Buffer.from("50C3B81347D82BC6", "hex"),
direction: "R",
dst: 255,
format: "YDRAW",
pgn: 129025,
prio: 2,
src: 127,
timestamp: new Date(
${today}T16:29:27.082Z),
};const n2kParts3 = parseN2kString(
"2016-04-09T16:41:09.078Z,3,127257,17,255,8,00,ff,7f,52,00,21,fe,ff"
);
const matches3 =
n2kParts3 ===
{
data: Buffer.from("00ff7f520021feff", "hex"),
dst: 255,
len: 8,
format: "Actisense",
pgn: 127257,
prio: 3,
src: 17,
timestamp: "2016-04-09T16:41:09.078Z",
};
``