UNPKG

@commaai/pandajs

Version:

JavaScript interface for communicating with Panda devices.

185 lines (155 loc) 4.52 kB
import USB from 'usb'; import { packCAN, unpackCAN } from 'can-message'; import Event from 'weakmap-event'; import { partial } from 'ap'; import wait from '../delay'; import isPromise from 'is-promise'; import net from 'net'; import dgram from 'dgram'; const PANDA_MESSAGE_ENDPOINT_NUMBER = 1; const PANDA_HOST = '192.168.0.10'; const PANDA_TCP_PORT = 1337; const PANDA_UDP_PORT = 1338; const REQUEST_OUT = 64; const REQUEST_IN = 192; const ErrorEvent = Event(); const ConnectEvent = Event(); const DisconnectEvent = Event(); const DataEvent = Event(); const MessageEvent = Event(); export default class Panda { constructor(options) { this.device = null; this.onError = partial(ErrorEvent.listen, this); this.onConnect = partial(ConnectEvent.listen, this); this.onDisconnect = partial(DisconnectEvent.listen, this); this.handleData = this.handleData.bind(this); this.handleError = this.handleError.bind(this); } async connectToTCP() { return new Promise((resolve, reject) => { var fail = (err) => { this.socket = null; ErrorEvent.broadcast(this, err); reject(err); }; var succeed = () => { this.socket.off('close', fail); this.socket.off('error', fail); resolve(); }; this.socket = net.connect(PANDA_TCP_PORT, PANDA_HOST); this.socket.on('connect', resolve); this.socket.on('close', fail); this.socket.on('error', fail); }); } async connect() { this.ignoreLengths = {}; await this.connectToTCP(); this.socket.on('data', this.handleData); this.socket.on('close', partial(DisconnectEvent.broadcast, this)); this.socket.on('error', this.handleError); return true; } async disconnect() { if (!this.socket) { return false; } await this.socket.close(); this.socket = null; return true; } async vendorRequest(data, length) { var data = await this.controlRead(REQUEST_OUT, data.request, data.value, data.index, length); return { data: Buffer.from(data), status: "ok" // hack, find out when it's actually ok }; } // not used anymore, but is nice for reference async nextFakeMessage() { await wait(10); return packCAN({ address: 0, busTime: ~~(Math.random() * 65000), data: ''.padEnd(16, '0'), bus: 0 }); } async controlRead(requestType, request, value, index, length) { if (!this.ignoreLengths[length]) { this.ignoreLengths[length] = 0; } this.ignoreLengths[length]++; const buf = Buffer.alloc(12); buf.writeUInt16LE(0, 0); buf.writeUInt16LE(0, 2); buf.writeUInt8(requestType, 4); buf.writeUInt8(request, 5); buf.writeUInt16LE(value, 6); buf.writeUInt16LE(index, 8); buf.writeUInt16LE(length, 10); this.socket.write(buf); return this.nextIncomingData(); } async nextIncomingData() { return new Promise((resolve, reject) => { once(partial(DataEvent.listen, this), resolve); }); } async nextIncomingMessage() { return new Promise((resolve, reject) => { once(partial(MessageEvent.listen, this), resolve); }); } async nextMessage() { var result = null; var attempts = 0; while (result === null) { try { return await this.bulkRead(1); } catch (err) { console.warn('can_recv failed, retrying'); attempts = Math.min(++attempts, 10); await wait(attempts * 100); } } } async handleData(buf) { const length = buf.readUInt32LE(0); const data = buf.slice(4, 4 + length); if (this.ignoreLengths[length]) { this.ignoreLengths[length]--; if (this.ignoreLengths[length] === 0) { delete this.ignoreLengths[length]; } DataEvent.broadcast(this, data); } else { MessageEvent.broadcast(this, data); } } async handleError(err) { if (err.errno === 'ETIMEDOUT') { DisconnectEvent.broadcast(this, err.errno); } else { ErrorEvent.broadcast(this, err); } } async bulkRead(endpoint: Number, timeoutMillis: Number = 0) { const promise = this.nextIncomingMessage(); const buf = Buffer.alloc(4); buf.writeUInt16LE(endpoint, 0); buf.writeUInt16LE(0, 2); this.socket.write(buf); return promise; } } function once (event, handler) { var unlisten = event(onceHandler); return unlisten; function onceHandler() { unlisten(); handler.apply(this, arguments); } }