UNPKG

@canboat/canboatjs

Version:

Native javascript version of canboat

108 lines 3.76 kB
"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