@canboat/canboatjs
Version:
Native javascript version of canboat
108 lines • 3.76 kB
JavaScript
"use strict";
/**
* Minimal CAN socket wrapper using fs streams instead of uv_poll_t.
*
* The native addon (native/canSocket.cpp) opens a PF_CAN socket, binds it
* to the interface, and returns the raw fd in blocking mode.
*
* Node.js's net.Socket cannot wrap CAN fds (uv_guess_handle returns
* UV_UNKNOWN_HANDLE). Instead we use fs.createReadStream which uses libuv's
* threadpool-based uv_fs_read — works on any fd, never stalls, and does not
* use uv_poll_t. Writes use a native non-blocking writeCanFrame() to avoid
* blocking libuv threadpool workers when the CAN bus has no listeners.
*
* Copyright 2025 Signal K contributors
* Licensed under the Apache License, Version 2.0
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CanChannel = void 0;
const events_1 = require("events");
const fs_1 = require("fs");
const CAN_EFF_FLAG = 0x80000000;
const CAN_EFF_MASK = 0x1fffffff;
const CAN_FRAME_SIZE = 16; // sizeof(struct can_frame)
const CAN_DATA_OFFSET = 8;
let native;
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
native = require('../build/Release/canSocket.node');
}
catch (_e) {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
native = require('../build/Debug/canSocket.node');
}
catch (_e) { }
}
/**
* Drop-in replacement for socketcan's channel object.
* Same API: addListener('onMessage'), addListener('onStopped'),
* start(), stop(), send(), removeAllListeners().
*/
class CanChannel extends events_1.EventEmitter {
readStream = null;
fd;
remainder = Buffer.alloc(0);
stopped = false;
constructor(ifname) {
super();
if (native === undefined) {
throw new Error('Failed to load native canSocket module');
}
this.fd = native.openCanSocket(ifname);
}
start() {
this.stopped = false;
this.readStream = (0, fs_1.createReadStream)('', {
fd: this.fd,
autoClose: false,
highWaterMark: CAN_FRAME_SIZE * 64
});
this.readStream.on('data', (chunk) => {
this.remainder = Buffer.concat([this.remainder, chunk]);
while (this.remainder.length >= CAN_FRAME_SIZE) {
const frame = this.remainder.subarray(0, CAN_FRAME_SIZE);
this.remainder = this.remainder.subarray(CAN_FRAME_SIZE);
const rawId = frame.readUInt32LE(0);
const id = rawId & CAN_EFF_MASK;
const dlc = frame[4];
const data = Buffer.from(frame.subarray(CAN_DATA_OFFSET, CAN_DATA_OFFSET + dlc));
this.emit('onMessage', { id, data });
}
});
this.readStream.on('error', (err) => {
if (!this.stopped) {
this.emit('onStopped', err.message);
}
});
this.readStream.on('close', () => {
if (!this.stopped) {
this.emit('onStopped', 'closed');
}
});
}
stop() {
this.stopped = true;
if (this.readStream) {
this.readStream.destroy();
this.readStream = null;
}
(0, fs_1.close)(this.fd, () => { });
}
send(msg) {
if (this.stopped) {
return;
}
const frame = Buffer.alloc(CAN_FRAME_SIZE);
let canId = msg.id;
if (msg.ext) {
canId |= CAN_EFF_FLAG;
}
frame.writeUInt32LE(canId >>> 0, 0);
frame[4] = msg.data.length;
msg.data.copy(frame, CAN_DATA_OFFSET, 0, Math.min(msg.data.length, 8));
native.writeCanFrame(this.fd, frame);
}
}
exports.CanChannel = CanChannel;
//# sourceMappingURL=canSocket.js.map