UNPKG

@essense/iso-claim

Version:

Library & background program that implements the ISO address claim procedure for CANbus (i.e. for NMEA2000 devices)

400 lines 34.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const canboatjs_1 = require("@canboat/canboatjs"); const int64_buffer_1 = require("int64-buffer"); const path_1 = require("path"); const fs_1 = __importDefault(require("fs")); const express_1 = __importDefault(require("express")); const debug_1 = __importDefault(require("debug")); const can_bus_1 = __importDefault(require("./can-bus")); let pkg = { version: '0.0.0' }; try { pkg = JSON.parse(fs_1.default.readFileSync(path_1.join(__dirname, '../../../package.json'), 'utf-8')); } catch (e) { pkg = { version: '-1.-1.-1' }; } const HOME = process.env.HOME; const version = pkg.version; const debug = debug_1.default('CANNode'); var ISOPGNs; (function (ISOPGNs) { ISOPGNs[ISOPGNs["REQUEST"] = 59904] = "REQUEST"; ISOPGNs[ISOPGNs["CLAIM"] = 60928] = "CLAIM"; ISOPGNs[ISOPGNs["NAK"] = 59392] = "NAK"; ISOPGNs[ISOPGNs["LIST"] = 126464] = "LIST"; ISOPGNs[ISOPGNs["INFO"] = 126996] = "INFO"; ISOPGNs[ISOPGNs["GROUP"] = 126208] = "GROUP"; })(ISOPGNs || (ISOPGNs = {})); class CANNode extends events_1.EventEmitter { constructor(bus, opts) { super(); this.READY = 2; this.ADDRESS_REQUESTED = 1; this.IDLE = 0; this.addressClaimTimeout = null; this.http = null; this.reconnect = true; this.options = Object.assign({ preferAddress: 0x5f }, opts); if (typeof this.options.uniqueNumber !== 'number' || this.options.uniqueNumber <= 0) { this.options.uniqueNumber = Math.floor(Math.random() * Math.floor(2097151)); } try { fs_1.default.unlinkSync(path_1.join(HOME, `.n2kaddress-${bus}.json`)); } catch (e) { debug(`Couldn't destory .n2kaddress.json: ${e.message}`); } this.reconnect = !!this.options.reconnect; this.state = this.IDLE; this.address = this.options.preferAddress; this.bus = new can_bus_1.default(bus); this.busname = bus; this.http = express_1.default(); this.bus.on('message', this.incomingMessage.bind(this)); this.bus.on('N2KMessage', (msg) => this.emit('N2KMessage', msg)); this.bus.on('error', (err) => this.emit('error', err)); this.bus.on('connected', () => this.emit('connected')); this.bus.on('disconnected', () => { this.emit('disconnected'); if (this.reconnect === true) { debug(`Reconnecting...`); this.emit('reconnecting'); this.bus.reconnect(); } }); this.http.get('/devices.html', this.showDevicesTable.bind(this)); this.http.get('/devices.json', this.showDevicesJSON.bind(this)); this.http.listen(this.options.port || 8001, () => console.log(`HTTP server listening on port ${this.options.port || 8001}`)); setTimeout(() => this.init(), 1000); } init() { if (this.bus.isConnected() === false) { const err = new Error('Not connected to bus'); this.emit('error', err); this.state = this.IDLE; return debug(err.message); } // Claim preferred address this.claimAddress(); } requestISO(PGN, dest = 255) { debug(`[requestISO] (${this.address} => ${dest}) requesting ISO response for PGN ${PGN}`); this.bus.send({ pgn: ISOPGNs.REQUEST, src: this.address, dest, data: { PGN } }); } showDevicesJSON(req, res) { debug('[showDevicesJSON] ' + req.path); const devices = this.bus.getDevices(); res.json(devices); } showDevicesTable(req, res) { debug('[showDevicesTable] ' + req.path); let html = '<table cellpadding="5" cellspacing="0" borders="0" width="100%" style="font-family: monospace">'; const devices = this.bus.getDevices(); html += '<thead>'; html += '<tr style="text-align: left font-family: monospace">'; html += '<th style="border-right: 1px solid grey border-bottom: 1px solid grey">SA</th>'; html += '<th style="border-right: 1px solid grey border-bottom: 1px solid grey">Manufacturer Code</th>'; html += '<th style="border-right: 1px solid grey border-bottom: 1px solid grey">Model ID</th>'; html += '<th style="border-right: 1px solid grey border-bottom: 1px solid grey">Serial #</th>'; html += '<th style="border-bottom: 1px solid grey">Product Code</th>'; html += '</tr>'; html += '</thead>'; html += '<tbody>'; html += '<tr style="text-align: left border-bottom: 1px solid grey font-family: monospace">'; html += `<td style="border-right: 1px solid grey border-bottom: 1px solid grey">${this.bus.getAddress()}</td>`; html += `<td style="border-right: 1px solid grey border-bottom: 1px solid grey">${this .options.manufacturerCode || 'Decipher Industries'}</td>`; html += `<td style="border-right: 1px solid grey border-bottom: 1px solid grey">${this .options.modelId || 'AP Controller'}</td>`; html += `<td style="border-right: 1px solid grey border-bottom: 1px solid grey">${this .options.modelSerialCode || String(version)}</td>`; html += `<td style="border-bottom: 1px solid grey">${this.options .productCode || 1337}</td>`; html += '</tr>'; Object.keys(devices).forEach((src) => { const device = devices[src]; if (!device || typeof device !== 'object') { return; } let { claim, info } = device; if (!claim || typeof claim !== 'object') { claim = {}; } if (!info || typeof info !== 'object') { info = {}; } html += '<tr style="text-align: left border-bottom: 1px solid grey font-family: monospace">'; html += `<td style="border-right: 1px solid grey border-bottom: 1px solid grey">${claim.src}</td>`; html += `<td style="border-right: 1px solid grey border-bottom: 1px solid grey">${claim['Manufacturer Code']}</td>`; html += `<td style="border-right: 1px solid grey border-bottom: 1px solid grey">${info['Model ID']}</td>`; html += `<td style="border-right: 1px solid grey border-bottom: 1px solid grey">${info['Model Serial Code']}</td>`; html += `<td style="border-bottom: 1px solid grey">${info['Product Code']}</td>`; html += '</tr>'; }); html += '</tbody>'; html += '</table>'; // html += `<pre>${JSON.stringify(devices, null, 2)}</pre>` res.set('Content-Type', 'text/html'); res.send(html); } incomingMessage(message) { const src = message.src; const pgn = message.pgn; const dest = message.dest; // We need to let through ISO address claim PGNs with the same source // so we can increase our address as needed if (pgn !== ISOPGNs.CLAIM && pgn !== ISOPGNs.LIST && src === this.address) { return debug(`[PGN ${pgn}] (${src} => ${dest}) came from us, ignoring.`); } debug(`${pgn} [${src} => ${dest}] ${message.data .toString() .replace(/(.{2})/g, '$1 ') .toUpperCase()}`); if (dest !== 255 && dest !== this.address) { return debug(`[PGN ${pgn}] (${src} => ${dest}) is not for us, ignoring.`); } switch (pgn) { case ISOPGNs.REQUEST: return this.handleISORequest(message); case ISOPGNs.GROUP: return this.handleGroupFunction(message); case ISOPGNs.CLAIM: return this.handleISOAddressClaim(message); case ISOPGNs.INFO: return this.handleProductInfo(message); case ISOPGNs.LIST: return this.handleSupportedPGNList(message); default: this.emit('message', message); break; } } addressClaimFields() { return { 'Device Class': this.options.deviceClass || 25, 'Device Function': this.options.deviceFunction || 130, 'Device Instance Lower': this.options.instanceLower || 0, 'Device Instance Upper': this.options.instanceUpper || 0, 'Industry Group': this.options.industryGroup || 4, 'Manufacturer Code': this.options.manufacturerCode || 999, Reserved1: 0, Reserved2: 1, 'System Instance': this.options.systemInstance || 0, 'Unique Number': this.options.uniqueNumber }; } claimAddress() { if (this.addressClaimTimeout !== null) { clearTimeout(this.addressClaimTimeout); this.addressClaimTimeout = null; } debug(`[claimAddress] (${this.address} => 255) claiming address ${this.address}`); this.bus.send({ pgn: ISOPGNs.CLAIM, dest: 255, src: this.address, data: this.addressClaimFields() }); if (this.state !== this.READY) { // Wait the required 250 ms, then indicate ready if not already ready. // If we had a conflict, this should have been resolved by now. this.state = this.ADDRESS_REQUESTED; setTimeout(() => this.setReady(), 250); } } setReady() { this.state = this.READY; this.bus.setAddress(this.address); // Request claimAddress from everyone else, to fill the device map. debug(`[setReady] Node ${this.address} is ready. Requesting addresses from everyone else.`); try { // Write our address to a file... fs_1.default.writeFileSync(path_1.join(HOME, `.n2kaddress-${this.busname}.json`), JSON.stringify({ timestamp: new Date().toISOString(), address: this.address, device: this.addressClaimFields() }, null, 2), 'utf-8'); } catch (e) { console.error(`Error writing .n2kaddress.json: ${e.message}`); process.exit(1); } this.requestISO(ISOPGNs.CLAIM); this.requestISO(ISOPGNs.INFO); this.requestISO(ISOPGNs.LIST); } NAKacknowledgement(PGN) { debug(`[NAKacknowledgement] (${this.address} => ${this.address}) PGN = ${PGN}`); this.bus.send({ pgn: ISOPGNs.NAK, dest: this.address, src: this.address, data: { Control: 1, 'Group Function': 255, PGN } }); } broadcastSupportedPGNs() { const data = { 'Function Code': 0, list: this.options.supportedPGNs || [] }; debug(`[broadcastSupportedPGNs] (${this.address} => 255) ${JSON.stringify(data, null, 2)}`); this.bus.send({ pgn: ISOPGNs.LIST, dest: 255, src: this.address, data }); } broadcastProductInfo() { const data = { 'Certification Level': this.options.certificationLevel || 0, 'Load Equivalency': this.options.loadEquivalency || 1, 'Model ID': this.options.modelId || 'EsSense Device', 'Model Serial Code': this.options.modelSerialCode || String(version), 'Model Version': this.options.modelVersion || 'CANbus ISO address arbitration library', 'NMEA 2000 Version': 2000, 'Product Code': this.options.productCode || 1337, 'Software Version Code': String(version) }; debug(`[broadcastProductInfo] (${this.address} => 255) ${JSON.stringify(data, null, 2)}`); this.bus.send({ pgn: ISOPGNs.INFO, dest: 255, src: this.address, data }); } handleISORequest(message) { const { src, dest, pgn, fields } = message; debug(`[handleISORequest] (${pgn}) ${src} => ${dest} { PGN: ${fields.PGN} }`); switch (fields.PGN) { case ISOPGNs.INFO: // Product info return this.broadcastProductInfo(); case ISOPGNs.CLAIM: // Address Claim return this.claimAddress(); case ISOPGNs.LIST: // Supported PGNs return this.broadcastSupportedPGNs(); default: // Anything else is unsupported. Send NAK return this.NAKacknowledgement(fields.PGN); } } handleGroupFunction(message) { const { src, dest, pgn, fields } = message; // Not supported. Send a response that indicates that we don't support this. switch (fields['Function Code']) { case 'Request': case 'Command': this.bus.send({ pgn: ISOPGNs.GROUP, dest: src, src: this.address, data: { '# of Parameters': 0, 'Function Code': 1, PGN: fields.PGN, 'PGN error code': 4, 'Transmission interval/Priority error code': 0 } }); return; default: return debug(`[handleGroupFunction] (${pgn}) ${src} => ${dest}: unknown function code (${fields['Function Code']})`); } } handleProductInfo(message) { const { src, dest, pgn, fields } = message; debug(`[handleProductInfo] (${pgn}) ${src} => ${dest}: ${JSON.stringify(fields, null, 2)}`); // Add or modify device on network this.bus.addDevice(src, { info: Object.assign({}, message) }); } handleSupportedPGNList(message) { const { src, dest, pgn, fields } = message; debug(`[handleSupportedPGNList] (${pgn}) ${src} => ${dest}: ${JSON.stringify(fields, null, 2)}`); // Add or modify device on network this.bus.addDevice(src, { supportedPGNs: Object.assign({}, message) }); } handleISOAddressClaim(message) { const { src, dest, pgn, fields } = message; debug(`[handleISOAddressClaim] (${pgn}) ${src} => ${dest}: ${JSON.stringify(fields, null, 2)}`); // Cache current network const network = this.bus.getDevices(); if (src !== this.address) { if (!network.hasOwnProperty(src)) { debug(`[handleISOAddressClaim] Adding node ${src} to network map`); this.bus.addDevice(src, { claim: Object.assign({}, message) }); } return; } const ours = addressClaimAsUint64({ pgn: ISOPGNs.CLAIM, dst: 255, src: this.address, data: this.addressClaimFields() }); const theirs = addressClaimAsUint64({ pgn, dest, src, data: fields }); debug(`[handleISOAddressClaim] Got competing address claim from ${src}, evaluating ours v. theirs: ${ours} v. ${theirs}`); if (ours < theirs) { debug(`[handleISOAddressClaim] Our int64 claim is less than theirs. Asserting superiority.`); return this.claimAddress(); } debug(`[handleISOAddressClaim] Their int64 claim is greater than or equal to ours. Increasing address.`); this.increaseAddress(); this.claimAddress(); } increaseAddress(incr = 1) { const attempt = (this.address + incr) % 253; const network = this.bus.getDevices(); if (attempt === this.address || typeof network[attempt] !== 'undefined') { debug(`[increaseAddress] attempt ${attempt} already taken, tring again`); this.increaseAddress(incr + 1); } else { debug(`[increaseAddress] setting address to: ${attempt}`); this.address = attempt; } } } // Utility copied from @canboat/canboatjs function addressClaimAsUint64(pgn) { return new int64_buffer_1.Uint64LE(canboatjs_1.toPgn(Object.assign(Object.assign({}, pgn), pgn.data))); } exports.default = CANNode; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FuLW5vZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbGliL2Nhbi1ub2RlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBQUEsbUNBQXFDO0FBQ3JDLGtEQUEwQztBQUMxQywrQ0FBdUM7QUFDdkMsK0JBQTJCO0FBQzNCLDRDQUFtQjtBQUNuQixzREFBNkI7QUFDN0Isa0RBQXlCO0FBQ3pCLHdEQUF3RTtBQUV4RSxJQUFJLEdBQUcsR0FBRyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsQ0FBQTtBQUU5QixJQUFJO0lBQ0YsR0FBRyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQ2QsWUFBRSxDQUFDLFlBQVksQ0FBQyxXQUFJLENBQUMsU0FBUyxFQUFFLHVCQUF1QixDQUFDLEVBQUUsT0FBTyxDQUFDLENBQ25FLENBQUE7Q0FDRjtBQUFDLE9BQU8sQ0FBQyxFQUFFO0lBQ1YsR0FBRyxHQUFHLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxDQUFBO0NBQzlCO0FBRUQsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUE7QUFDN0IsTUFBTSxPQUFPLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQTtBQUMzQixNQUFNLEtBQUssR0FBRyxlQUFLLENBQUMsU0FBUyxDQUFDLENBQUE7QUErQjlCLElBQUssT0FPSjtBQVBELFdBQUssT0FBTztJQUNWLCtDQUFlLENBQUE7SUFDZiwyQ0FBYSxDQUFBO0lBQ2IsdUNBQVcsQ0FBQTtJQUNYLDBDQUFhLENBQUE7SUFDYiwwQ0FBYSxDQUFBO0lBQ2IsNENBQWMsQ0FBQTtBQUNoQixDQUFDLEVBUEksT0FBTyxLQUFQLE9BQU8sUUFPWDtBQUVELE1BQU0sT0FBUSxTQUFRLHFCQUFZO0lBU2hDLFlBQVksR0FBVyxFQUFFLElBQW9CO1FBQzNDLEtBQUssRUFBRSxDQUFBO1FBVEMsVUFBSyxHQUFXLENBQUMsQ0FBQTtRQUNqQixzQkFBaUIsR0FBVyxDQUFDLENB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