UNPKG

@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
"use strict"; 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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