UNPKG

@canboat/canboatjs

Version:

Native javascript version of canboat

185 lines 6.75 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildMaretronConfigCommand = buildMaretronConfigCommand; exports.buildMaretronConfigCommandActisense = buildMaretronConfigCommandActisense; exports.parseMaretronConfigResponse = parseMaretronConfigResponse; exports.getMaretronProductName = getMaretronProductName; exports.getMaretronOpcodeName = getMaretronOpcodeName; const ts_pgns_1 = require("@canboat/ts-pgns"); // Build reverse lookups from ts-pgns enums: numeric code → name string const PRODUCT_NAMES = {}; for (const [name, code] of Object.entries(ts_pgns_1.MaretronProductCodeValues)) { PRODUCT_NAMES[code] = name; } const OPCODE_NAMES = {}; for (const [name, code] of Object.entries(ts_pgns_1.MaretronOpcodeValues)) { OPCODE_NAMES[code] = name; } /** * Build a raw PGN 126208 Command payload targeting PGN 126720 for Maretron * proprietary device configuration. This bypasses canboatjs's structured * 126208 encoder because Maretron's field index scheme doesn't match * canboat's semantic field ordering for PGN 126720. * * Wire format (confirmed from live capture): * ``` * [0] FC = 0x01 (Command) * [1-3] Target PGN = 0x01EF00 (126720) LE * [4] Priority + Reserved = 0xF8 * [5] Number of params = 0x04 * [6] Field idx 1 * [7-8] Mfr code packed = 0x9889 (mfr=137, rsvd=1, ind=4) * [9] Field idx 2 * [10-11] Product code (LE) * [12] Field idx 3 * [13-14] Sub-header = 0x0001 * [15+] Opcode + payload bytes * ``` * * @param dst Destination device address on the N2K bus * @param productCode Maretron product code (e.g. 23603 for SIM100) * @param opcodePayload Array of bytes: [opcode, ...data] * @returns PGN object suitable for emission via nmea2000JsonOut */ function buildMaretronConfigCommand(dst, productCode, opcodePayload) { const payload = [ 0x01, // Function Code: Command 0x00, 0xef, 0x01, // Target PGN 126720 (0x01EF00) LE 0xf8, // Priority=15 (leave unchanged) + reserved 0x04, // Number of parameters 0x01, // Field index 1: manufacturer code 0x89, 0x98, // Packed: mfr=137(0x89), reserved=1, industry=4 → 0x9889 LE 0x02, // Field index 2: product code productCode & 0xff, (productCode >> 8) & 0xff, // Product code LE 0x03, // Field index 3: sub-header 0x01, 0x00, // Sub-header = 0x0001 ...opcodePayload ]; const hexData = payload.map((b) => b.toString(16).padStart(2, '0')).join(','); return { pgn: 126208, prio: 3, dst, src: 0, fields: { data: hexData } }; } /** * Build a raw PGN 126208 Command as an actisense-format hex string, * suitable for emission via the nmea2000out event. * * @param dst Destination device address on the N2K bus * @param productCode Maretron product code * @param opcodePayload Array of bytes: [opcode, ...data] * @returns Actisense serial format string */ function buildMaretronConfigCommandActisense(dst, productCode, opcodePayload) { const payload = [ 0x01, 0x00, 0xef, 0x01, 0xf8, 0x04, 0x01, 0x89, 0x98, 0x02, productCode & 0xff, (productCode >> 8) & 0xff, 0x03, 0x01, 0x00, ...opcodePayload ]; const now = new Date(); const timestamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}T${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}.${String(now.getMilliseconds()).padStart(3, '0')}Z`; const hexBytes = payload .map((b) => b.toString(16).padStart(2, '0').toUpperCase()) .join(','); return `${timestamp},3,126208,0,${dst},${payload.length},${hexBytes}`; } /** * Parse a decoded PGN 126720 that matches the Maretron proprietary * configuration format. Works with both the new maretronProprietaryConfiguration * variant (which has named fields) and the generic maretronSlaveResponse. * * For maretronProprietaryConfiguration: * fields.productCode, fields.softwareCode, fields.opcode, fields.payload * * For raw PGN objects with a data field, extracts bytes directly. * * @param pgn A decoded PGN 126720 object * @returns Parsed Maretron config info, or null if not a Maretron config PGN */ function parseMaretronConfigResponse(pgn) { if (!pgn || pgn.pgn !== 126720) { return null; } const fields = pgn.fields; // Check for Maretron manufacturer code if (fields?.manufacturerCode !== 'Maretron' && fields?.manufacturerCode !== 137) { return null; } // New maretronProprietaryConfiguration variant with named fields if (fields.opcode !== undefined) { const productCode = typeof fields.productCode === 'number' ? fields.productCode : 0; const softwareCode = typeof fields.softwareCode === 'number' ? fields.softwareCode : 0; const opcode = typeof fields.opcode === 'number' ? fields.opcode : 0; let payloadBytes = []; if (fields.payload) { if (typeof fields.payload === 'string') { payloadBytes = fields.payload .split(',') .map((s) => parseInt(s, 16)); } else if (Buffer.isBuffer(fields.payload)) { payloadBytes = Array.from(fields.payload); } } return { productCode, productName: PRODUCT_NAMES[productCode], softwareCode, opcode, opcodeName: OPCODE_NAMES[opcode], payload: payloadBytes }; } // Legacy maretronSlaveResponse: command field maps to opcode if (fields.command !== undefined) { const productCode = typeof fields.productCode === 'number' ? fields.productCode : 0; const softwareCode = typeof fields.softwareCode === 'number' ? fields.softwareCode : 0; const opcode = typeof fields.command === 'number' ? fields.command : 0; return { productCode, productName: PRODUCT_NAMES[productCode], softwareCode, opcode, opcodeName: OPCODE_NAMES[opcode], payload: [] }; } return null; } /** * Get the product name for a Maretron product code. */ function getMaretronProductName(productCode) { return PRODUCT_NAMES[productCode]; } /** * Get the opcode name for a Maretron config opcode. */ function getMaretronOpcodeName(opcode) { return OPCODE_NAMES[opcode]; } //# sourceMappingURL=maretron.js.map