@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
JavaScript
;
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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