@essense/iso-claim
Version:
Library & background program that implements the ISO address claim procedure for CANbus (i.e. for NMEA2000 devices)
288 lines • 24.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const debug_1 = __importDefault(require("debug"));
const events_1 = require("events");
const canboatjs_1 = require("@canboat/canboatjs");
// @ts-ignore
const socketcan_1 = require("socketcan");
const debug = debug_1.default('CANBus');
class CANBus extends events_1.EventEmitter {
constructor(bus) {
super();
this.onChannelMessage = (msg) => {
const message = this.CANtoN2Kmessage(msg);
this.emit('N2KMessage', message);
try {
this.parser.parsePgnData(message, message.length, message.data);
}
catch (e) {
debug('Error whilst parsing message: ' + e.message);
}
};
this.onChannelStopped = () => {
this.open = false;
this.channel = null;
this.emit('disconnected');
debug(`Disconnected from ${this.bus}`);
};
this.network = {};
this.address = -1;
this.bus = bus || 'can0';
this.channel = null;
this.open = false;
this.parser = new canboatjs_1.FromPgn();
this.parser.on('pgn', (pgn) => {
this.handlePGN(pgn);
});
this.parser.on('error', (pgn, err) => {
debug(`Error for pgn ${pgn}: ${err}`);
this.emit('error', err);
});
this.parser.on('warning', (pgn, err) => {
debug(`Warning for pgn ${pgn}: ${err}`);
this.emit('warning', err);
});
this.connect();
}
getAddress() {
return this.address;
}
setAddress(address) {
this.address = address;
}
addDevice(src, device) {
this.network = Object.assign(Object.assign({}, this.network), { [src]: Object.assign(Object.assign({}, (this.network[src] || {})), device) });
// Clean up raw IDs & raw can data
Object.keys(this.network).forEach(source => {
Object.keys(this.network[source]).forEach(key => {
let node = this.network[source][key];
if (!node || typeof node !== 'object') {
return;
}
// Remove raw CAN data
if (node.hasOwnProperty('data')) {
delete node.data;
}
// Remove raw CAN ID
if (node.hasOwnProperty('id')) {
delete node.id;
}
// Flatten fields
if (node.hasOwnProperty('fields')) {
node = Object.assign(Object.assign({}, node), node.fields);
delete node.fields;
}
this.network[source][key] = Object.assign({}, node);
});
});
debug(`[addDevice] adding/modifying device ${src}. Network has changed: ${JSON.stringify(this.network, null, 2)}`);
}
getDevices(array = false) {
const devices = Object.assign({}, this.network);
if (array === true) {
return Object.keys(devices).reduce((list, src) => {
list.push({
src,
device: devices[src]
});
return list;
}, []);
}
return devices;
}
reconnect() {
// @FIXME: do we need to do cleanup?
// if (this.channel) {
// this.channel.removeListener('onStopped')
// this.channel.removeListener('onMessage')
// this.open = false
// this.channel = null
// }
// Reconnect after 200 ms; assuming the CAN bus is set up with "reconnect-ms 100".
setTimeout(() => this.connect(), 200);
}
connect() {
try {
this.channel = socketcan_1.createRawChannel(this.bus, true);
this.open = true;
this.emit('connected');
debug(`Connected to ${this.bus}`);
this.channel.addListener('onStopped', this.onChannelStopped);
this.channel.addListener('onMessage', this.onChannelMessage);
this.channel.start();
}
catch (err) {
debug(`Error opening channel: ${err.message}`);
this.emit('error', err);
}
}
send(message) {
if (this.open === false ||
!this.channel ||
typeof this.channel.send !== 'function') {
this.emit('error', new Error(`Can't send message before bus is open: ${JSON.stringify(message)}`));
}
// 60928 = address claim
if (this.address === -1 && message.pgn !== 60928) {
this.emit('error', new Error(`Can't send messages before an address is claimed`));
}
// Fix discrepancies between canboatjs & internal format
if (message.hasOwnProperty('dest') && !message.hasOwnProperty('dst')) {
message.dst = message.dest;
}
// Fix discrepancies between canboatjs & internal format
if (message.hasOwnProperty('dst') && !message.hasOwnProperty('dest')) {
message.dest = message.dst;
}
if (message.dest === 0 || message.dst === 0) {
message.dest = 255;
message.dst = 255;
}
const frame = {
id: this.PGNtoID(message.pgn, message.src, message.dst),
ext: true,
data: canboatjs_1.toPgn(Object.assign(Object.assign({}, message), message.data))
};
let frames = [frame];
// If it's actually a fast-packet
if (frame.data.length > 8) {
frames = this.generateCANFrames(frame);
}
try {
debug(`[send] Sending ${frames.length} CAN frames...`);
frames.forEach((canframe, index) => {
debug(`[send] (${index + 1} / ${frames.length}) Sending CAN frame with ID ${canframe.id} - ${JSON.stringify(canframe.data)}`);
this.channel.send(canframe);
});
}
catch (e) {
debug(`[send] Error sending PGN: ${e.message}`);
this.emit('error', e);
}
}
sendRawPgn(pgn, data, dest = 255) {
if (this.open === false ||
!this.channel ||
typeof this.channel.send !== 'function') {
this.emit('error', new Error(`Can't send message before bus is open: ${JSON.stringify({ pgn, data, dest })}`));
}
// 60928 = address claim
if (this.address === -1 && pgn !== 60928) {
this.emit('error', new Error(`Can't send messages before an address is claimed`));
}
const frame = {
id: this.PGNtoID(pgn, this.address, dest),
ext: true,
data
};
let frames = [frame];
// If it's actually a fast-packet
if (frame.data.length > 8) {
frames = this.generateCANFrames(frame);
}
try {
debug(`[send] Sending ${frames.length} CAN frames...`);
frames.forEach((canframe, index) => {
debug(`[send] (${index + 1} / ${frames.length}) Sending CAN frame with ID ${canframe.id} - ${JSON.stringify(canframe.data)}`);
this.channel.send(canframe);
});
}
catch (e) {
debug(`[send] Error sending PGN: ${e.message}`);
this.emit('error', e);
}
}
isConnected() {
return this.open;
}
// @FIXME prio is always 6 atm
PGNtoID(pgn, src, dest = 255, prio = 6) {
let id = src | 0x80000000;
if ((pgn & 0xff) === 0) {
// PDU 1
id += dest << 8;
id += pgn << 8;
id += prio << 26;
}
else {
// PDU 2
id += pgn << 8;
id += prio << 26;
}
return id;
}
IDtoPGN(id) {
let idString = id.toString(2);
const missingZeroes = 32 - idString.length;
for (let i = 0; i < missingZeroes; i += 1) {
idString = `0${idString}`;
}
const DP = `0000000${idString[7]}`;
const PF = idString.slice(8, 16);
const PS = idString.slice(16, 24);
const SA = idString.slice(24, 32);
// debug(`DP: ${parseInt(DP, 2)}, PF: ${parseInt(PF, 2)}, PS: ${parseInt(PS, 2)}, SA: ${parseInt(SA, 2)}`)
return {
id,
pgn: parseInt(PF, 2) < 240
? (parseInt(DP, 2) << 16) + (parseInt(PF, 2) << 8)
: (parseInt(DP, 2) << 16) + (parseInt(PF, 2) << 8) + parseInt(PS, 2),
dest: parseInt(PF, 2) < 240 ? parseInt(PS, 2) : 0xff,
src: parseInt(SA, 2)
};
}
CANtoN2Kmessage(message) {
const PGN = this.IDtoPGN(message.id);
return {
data: message.data,
pgn: PGN.pgn,
src: PGN.src,
dest: PGN.dest,
id: message.id,
timestamp: message.timestamp || new Date().toISOString(),
length: message.data.length
};
}
generateCANFrames(frame) {
const buffer = Buffer.from(frame.data);
const frames = [];
let cursor = 0x40;
const first = Buffer.alloc(8);
first.writeUInt8(cursor++, 0);
first.writeUInt8(buffer.length, 1);
// Copy from source "buffer" byte 0 - byte 6 into target "first", starting at target byte index 2
buffer.copy(first, 2, 0, 6);
frames.push(Object.assign(Object.assign({}, frame), { data: Buffer.from(first) }));
// loop over each byte (starting at 6) in "buffer", increasing the loop index by 7 bytes for each iteration
for (let i = 6; i < buffer.length; i += 7) {
const next = Buffer.alloc(8);
let end = i + 7; // end byte index of the slice to take from "buffer"
let fill = 0;
// If the end is beyond "buffer" length, set a variable "fill" with the number of padding bytes to add
if (end > buffer.length) {
fill = end - buffer.length;
end = buffer.length;
}
// Write the next cursor at byte 0 in the current frame
next.writeUInt8(cursor++, 0);
// Copy from source "buffer" byte i - byte end into target "next", starting at target byte index 1
buffer.copy(next, 1, i, end);
// pad the buffer if required
if (fill > 0) {
for (let j = end - i; j < 8; j++) {
next.writeUInt8(0xff, j);
}
}
frames.push(Object.assign(Object.assign({}, frame), { data: Buffer.from(next) }));
}
return frames;
}
handlePGN(message) {
this.emit('message', message);
}
}
exports.default = CANBus;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FuLWJ1cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvY2FuLWJ1cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLGtEQUF5QjtBQUN6QixtQ0FBcUM7QUFDckMsa0RBQW1EO0FBQ25ELGFBQWE7QUFDYix5Q0FBNEM7QUFFNUMsTUFBTSxLQUFLLEdBQUcsZUFBSyxDQUFDLFFBQVEsQ0FBQyxDQUFBO0FBdUQ3QixNQUFNLE1BQU8sU0FBUSxxQkFBWTtJQUMvQixZQUFZLEdBQVc7UUFDckIsS0FBSyxFQUFFLENBQUE7UUFvWEQscUJBQWdCLEdBQUcsQ0FBQyxHQUFlLEVBQVEsRUFBRTtZQUNuRCxNQUFNLE9BQU8sR0FBZSxJQUFJLENBQUMsZUFBZSxDQUFDLEdBQUcsQ0FBQyxDQUFBO1lBQ3JELElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxDQUFBO1lBRWhDLElBQUk7Z0JBQ0YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFBO2FBQ2hFO1lBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ1YsS0FBSyxDQUFDLGdDQUFnQyxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQTthQUNwRDtRQUNILENBQUMsQ0FBQTtRQUVPLHFCQUFnQixHQUFHLEdBQVMsRUFBRTtZQUNwQyxJQUFJLENBQUMsSUFBSSxHQUFHLEtBQUssQ0FBQTtZQUNqQixJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQTtZQUNuQixJQUFJLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFBO1lBQ3pCLEtBQUssQ0FBQyxxQkFBcUIsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUE7UUFDeEMsQ0FBQyxDQUFBO1FBbFlDLElBQUksQ0FBQyxPQUFPLEdBQUcsRUFBRSxDQUFBO1FBQ2pCLElBQUksQ0FBQyxP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