diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..d8099b3 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,22 @@ +name: Build and test pandajs + +on: [push, pull_request] + +jobs: + build_test: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + id: yarn-cache + with: + path: node_modules + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - if: steps.yarn-cache.outputs.cache-hit != 'true' + run: | + yarn install --ignore-scripts + yarn add --ignore-scripts usb + - run: yarn build + - run: yarn test diff --git a/README.md b/README.md index ec01cd8..bb3693d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ or import Panda from '@commaai/pandajs'; // create instance -var panda = new Panda(); +const panda = new Panda(); // register listener panda.onMessage((msg) => { diff --git a/control-panda.js b/control-panda.js index 7e8412a..b5b0ea0 100755 --- a/control-panda.js +++ b/control-panda.js @@ -3,7 +3,6 @@ const cli = require('commander'); const PandaLib = require('./lib'); const Panda = PandaLib.default; -const wait = require('./src/delay'); const package = require('./package'); cli @@ -57,7 +56,7 @@ if (!process.argv.slice(2).length) { cli.help(); } -var safetyModes = []; +const safetyModes = []; Object.keys(PandaLib).forEach(function (key) { if (key.startsWith('SAFETY_')) { safetyModes.push(key.substr(7).toLowerCase()); @@ -67,7 +66,7 @@ Object.keys(PandaLib).forEach(function (key) { cli.parse(process.argv); async function setupPanda () { - var panda = new Panda({ + const panda = new Panda({ wifi: cli.wifi }); @@ -84,37 +83,37 @@ async function setupPanda () { // command implementations async function getVersion () { - var panda = await setupPanda(); - var result = await panda.getVersion(); + const panda = await setupPanda(); + const result = await panda.getVersion(); console.log(result); } async function getSecret () { - var panda = await setupPanda(); - var result = await panda.getSecret(); + const panda = await setupPanda(); + const result = await panda.getSecret(); console.log(result); } async function isWhite () { - var panda = await setupPanda(); - var result = await panda.isWhite(); + const panda = await setupPanda(); + const result = await panda.isWhite(); console.log('is white:', result); } async function isGrey () { - var panda = await setupPanda(); - var result = await panda.isGrey(); + const panda = await setupPanda(); + const result = await panda.isGrey(); console.log('is grey:', result); } async function isBlack () { - var panda = await setupPanda(); - var result = await panda.isBlack(); + const panda = await setupPanda(); + const result = await panda.isBlack(); console.log('is black:', result); } async function hasObd () { - var panda = await setupPanda(); - var result = await panda.hasObd(); + const panda = await setupPanda(); + const result = await panda.hasObd(); console.log('has OBD port:', result); } @@ -125,8 +124,8 @@ async function setObd (connected, cmd) { } obd = connected === "true" || connected === "1"; console.log('OBD port:', obd ? 'connected' : 'disconnected'); - var panda = await setupPanda(); - var result = await panda.setObd(obd); + const panda = await setupPanda(); + const result = await panda.setObd(obd); console.log(result); } @@ -135,21 +134,21 @@ async function setSafetyMode (mode, cmd) { console.error('Safety mode must be one of the following:', '\n\t' + safetyModes.join('\n\t')); return; } - var modeConst = PandaLib['SAFETY_' + mode.toUpperCase()]; + const modeConst = PandaLib['SAFETY_' + mode.toUpperCase()]; console.log('Activing safety mode:', mode, '(0x' + modeConst.toString(16) + ')'); - var panda = await setupPanda(); - var result = await panda.setSafetyMode(modeConst); + const panda = await setupPanda(); + const result = await panda.setSafetyMode(modeConst); console.log(result); } async function getWifi () { - var panda = await setupPanda(); - var result = await panda.getDeviceMetadata(); + const panda = await setupPanda(); + const result = await panda.getDeviceMetadata(); console.log('SID: panda-' + result[0]); console.log('Password:', result[1]); } async function getHealth () { - var panda = await setupPanda(); + const panda = await setupPanda(); console.log(await panda.getHealth()); } diff --git a/dump-can.js b/dump-can.js index e918ccd..15b1408 100755 --- a/dump-can.js +++ b/dump-can.js @@ -15,7 +15,7 @@ cli .option('-i, --index ', 'Choose a different connected panda than the first one (zero indexed)', parseInt) .parse(process.argv); -var panda = new Panda({ +const panda = new Panda({ wifi: cli.wifi, selectDevice: (devices) => { return devices[Math.min(devices.length, cli.index || 0)]; @@ -62,7 +62,7 @@ connectAndRun(); async function connectAndRun () { await panda.connect(); if (cli.health) { - var health = await panda.getHealth(); + const health = await panda.getHealth(); console.log(health); console.log('Connect finished, waiting then reading all messages...'); await wait(1000); diff --git a/src/browser.js b/src/browser.js index 5c6e845..3ee3f33 100644 --- a/src/browser.js +++ b/src/browser.js @@ -24,7 +24,7 @@ export const SAFETY_HONDA_BOSCH_HARNESS = 20; export default function Panda (options) { options = options || {}; - var device = new PandaDevice(options, navigator.usb); + const device = new PandaDevice(options, navigator.usb); options.device = device; return new PandaAPI(options); } diff --git a/src/delay.js b/src/delay.js index a208257..767480a 100644 --- a/src/delay.js +++ b/src/delay.js @@ -1,34 +1,34 @@ 'use strict'; class CancelError extends Error { - constructor(message) { - super(message); - this.name = 'CancelError'; - } + constructor(message) { + super(message); + this.name = 'CancelError'; + } } const createDelay = willResolve => (ms, value) => { - let timeoutId; - let internalReject; + let timeoutId; + let internalReject; - const delayPromise = new Promise((resolve, reject) => { - internalReject = reject; + const delayPromise = new Promise((resolve, reject) => { + internalReject = reject; - timeoutId = setTimeout(() => { - const settle = willResolve ? resolve : reject; - settle(value); - }, ms); - }); + timeoutId = setTimeout(() => { + const settle = willResolve ? resolve : reject; + settle(value); + }, ms); + }); - delayPromise.cancel = () => { - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = null; - internalReject(new CancelError('Delay canceled')); - } - }; + delayPromise.cancel = () => { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + internalReject(new CancelError('Delay canceled')); + } + }; - return delayPromise; + return delayPromise; }; module.exports = createDelay(true); diff --git a/src/impl/browser.js b/src/impl/browser.js index 4cef889..93b2c3e 100644 --- a/src/impl/browser.js +++ b/src/impl/browser.js @@ -1,7 +1,6 @@ -import { packCAN, unpackCAN } from 'can-message'; +import { packCAN } from 'can-message'; import Event from 'weakmap-event'; import { partial } from 'ap'; -import now from 'performance-now'; import wait from '../delay'; const PANDA_VENDOR_ID = 0xbbaa; @@ -54,12 +53,11 @@ export default class Panda { index: data.index }; - var result = await this.device.controlTransferIn(controlParams, length); - result = { + const result = await this.device.controlTransferIn(controlParams, length); + return { data: Buffer.from(result.data.buffer), status: result.status }; - return result; } async vendorWrite(data, length) { @@ -90,8 +88,8 @@ export default class Panda { } async nextMessage() { - var result = null; - var attempts = 0; + let result = null; + let attempts = 0; while (result === null) { try { diff --git a/src/impl/node.js b/src/impl/node.js index db2b488..ebe0825 100644 --- a/src/impl/node.js +++ b/src/impl/node.js @@ -1,8 +1,7 @@ import USB from 'usb'; -import { packCAN, unpackCAN } from 'can-message'; +import { packCAN } from 'can-message'; import Event from 'weakmap-event'; import { partial } from 'ap'; -import now from 'performance-now'; import wait from '../delay'; import isPromise from 'is-promise'; @@ -26,9 +25,7 @@ export default class Panda { } async findDevice() { - var devices = USB.getDeviceList(); - - devices = devices.filter((device) => { + const devices = USB.getDeviceList().filter((device) => { return device.deviceDescriptor.idVendor === PANDA_VENDOR_ID; }); @@ -36,7 +33,7 @@ export default class Panda { } async selectDevice(devices) { return new Promise((resolve, reject) => { - var result = this.selectDeviceMethod(devices, resolve); + const result = this.selectDeviceMethod(devices, resolve); if (result) { if (isPromise(result)) { @@ -141,7 +138,7 @@ export default class Panda { endpointNumber = endpointNumber | 0x80; return new Promise(async (resolve, reject) => { - var endpoint = null; + let endpoint = null; this.device.interfaces.some(iface => { const epoint = iface.endpoint(endpointNumber); @@ -151,19 +148,19 @@ export default class Panda { } }); if (!endpoint) { - let err = new Error('PandaJS: nodeusb: transferIn failed to find endpoint interface ' + endpointNumber); + const err = new Error('PandaJS: nodeusb: transferIn failed to find endpoint interface ' + endpointNumber); ErrorEvent.broadcast(this, err); return reject(err); } if (endpoint.direction !== 'in') { - let err = new Error('PandaJS: nodeusb: endpoint interface is ' + endpoint.direction + ' instead of in'); + const err = new Error('PandaJS: nodeusb: endpoint interface is ' + endpoint.direction + ' instead of in'); ErrorEvent.broadcast(this, err); return reject(err); } - var data = Buffer.from([]); - while (data.length === 0) { + let data; + do { data = await this.endpointTransfer(endpoint, length); - } + } while (data.length === 0); resolve(data); }); } @@ -180,10 +177,9 @@ export default class Panda { } async nextMessage() { - var result = null; - var attempts = 0; + let attempts = 0; - while (result === null) { + while (true) { try { return await this.transferIn(1, BUFFER_SIZE); } catch (err) { diff --git a/src/impl/node.test.js b/src/impl/node.test.js index 17f246d..2424f50 100644 --- a/src/impl/node.test.js +++ b/src/impl/node.test.js @@ -8,7 +8,7 @@ const desiredDeviceString = 'it worked'; const deviceList = [false, false, desiredDeviceString, false]; test('selectDevice can return any way', async function (t) { - var panda = new PandaNode({ + let panda = new PandaNode({ selectDevice: selectDeviceReturn }); t.equals(await panda.selectDevice(deviceList), desiredDeviceString, 'works when returning directly'); diff --git a/src/impl/wifi.js b/src/impl/wifi.js index 6b85ab0..61ef8b6 100644 --- a/src/impl/wifi.js +++ b/src/impl/wifi.js @@ -1,11 +1,8 @@ -import USB from 'usb'; -import { packCAN, unpackCAN } from 'can-message'; +import { packCAN } 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'; @@ -33,12 +30,12 @@ export default class Panda { } async connectToTCP() { return new Promise((resolve, reject) => { - var fail = (err) => { + const fail = (err) => { this.socket = null; ErrorEvent.broadcast(this, err); reject(err); }; - var succeed = () => { + const succeed = () => { this.socket.off('close', fail); this.socket.off('error', fail); resolve(); @@ -72,9 +69,9 @@ export default class Panda { } async vendorRequest(data, length) { - var data = await this.controlRead(REQUEST_OUT, data.request, data.value, data.index, length); + const response = await this.controlRead(REQUEST_OUT, data.request, data.value, data.index, length); return { - data: Buffer.from(data), + data: Buffer.from(response), status: "ok" // hack, find out when it's actually ok }; } @@ -124,10 +121,9 @@ export default class Panda { } async nextMessage() { - var result = null; - var attempts = 0; + let attempts = 0; - while (result === null) { + while (true) { try { return await this.bulkRead(1); } catch (err) { @@ -161,24 +157,24 @@ export default class Panda { } async bulkRead(endpoint: Number, timeoutMillis: Number = 0) { - const promise = this.nextIncomingMessage(); + const promise = this.nextIncomingMessage(); - const buf = Buffer.alloc(4); - buf.writeUInt16LE(endpoint, 0); - buf.writeUInt16LE(0, 2); - this.socket.write(buf); + const buf = Buffer.alloc(4); + buf.writeUInt16LE(endpoint, 0); + buf.writeUInt16LE(0, 2); + this.socket.write(buf); - return promise; + return promise; } } function once (event, handler) { - var unlisten = event(onceHandler); - - return unlisten; + const unlisten = event(onceHandler); function onceHandler() { unlisten(); handler.apply(this, arguments); } + + return unlisten; } diff --git a/src/index.js b/src/index.js index 2aaeb92..1e1aaee 100644 --- a/src/index.js +++ b/src/index.js @@ -25,7 +25,7 @@ export default function Panda (options) { options = options || {}; options.selectDevice = options.selectDevice || selectFirstDevice; - var device = new PandaDevice(options); + const device = new PandaDevice(options); options.device = device; return new PandaAPI(options); } diff --git a/src/panda-device.js b/src/panda-device.js index f13121e..4c4b3ac 100644 --- a/src/panda-device.js +++ b/src/panda-device.js @@ -1,23 +1,22 @@ - export default function PandaUSB (options) { if (require('is-browser')) { if (options.wifi) { throw new Error('You cannot use wifi mode in the browser.'); } - let PandaWebUSB = require('./impl/browser').default; + const PandaWebUSB = require('./impl/browser').default; return new PandaWebUSB(options, navigator.usb); } if (options.wifi) { - let PandaWifi = require('./impl/wifi').default; + const PandaWifi = require('./impl/wifi').default; return new PandaWifi(options); } // check for test before node since tests always run in node if (isTestEnv()) { - let PandaMock = require('./impl/mock').default; + const PandaMock = require('./impl/mock').default; return new PandaMock(options); } if (require('is-node')) { - let PandaNodeUSB = require('./impl/node').default; + const PandaNodeUSB = require('./impl/node').default; return new PandaNodeUSB(options); } console.log(process.env); diff --git a/src/panda.js b/src/panda.js index 41acd9f..cab2150 100644 --- a/src/panda.js +++ b/src/panda.js @@ -1,4 +1,4 @@ -import { packCAN, unpackCAN } from 'can-message'; +import { unpackCAN } from 'can-message'; import Event from 'weakmap-event'; import { partial } from 'ap'; import now from 'performance-now'; @@ -53,7 +53,7 @@ export default class Panda { } await this.device.connect(); - var serialNumber = await this.getSerialNumber(); + const serialNumber = await this.getSerialNumber(); this.connectHandler(serialNumber); return serialNumber; @@ -69,7 +69,7 @@ export default class Panda { return this.unpause(); } async pause() { - var wasPaused = this.isPaused(); + const wasPaused = this.isPaused(); this.paused = true; return !wasPaused; @@ -78,7 +78,7 @@ export default class Panda { return this.unpause(); } async unpause() { - var wasPaused = this.isPaused(); + const wasPaused = this.isPaused(); if (!wasPaused) { return false; } @@ -91,53 +91,45 @@ export default class Panda { // vendor API methods async getHealth() { - let buf = await this.vendorRequest('health', { + const buf = await this.vendorRequest('health', { request: 0xd2, value: 0, index: 0 }, 13); - let voltage = buf.readUInt32LE(0) / 1000; - let current = buf.readUInt32LE(4) / 1000; - let isStarted = buf.readInt8(8) === 1; - let controlsAreAllowed = buf.readInt8(9) === 1; - let isGasInterceptorDetector = buf.readInt8(10) === 1; - let isStartSignalDetected = buf.readInt8(11) === 1; - let isStartedAlt = buf.readInt8(12) === 1; - return { - voltage, - current, - isStarted, - controlsAreAllowed, - isGasInterceptorDetector, - isStartSignalDetected, - isStartedAlt + voltage: buf.readUInt32LE(0) / 1000, + current: buf.readUInt32LE(4) / 1000, + isStarted: buf.readInt8(8) === 1, + controlsAreAllowed: buf.readInt8(9) === 1, + isGasInterceptorDetector: buf.readInt8(10) === 1, + isStartSignalDetected: buf.readInt8(11) === 1, + isStartedAlt: buf.readInt8(12) === 1 }; } async getDeviceMetadata() { - let buf = await this.vendorRequest('getDeviceMetadata', { + const buf = await this.vendorRequest('getDeviceMetadata', { request: 0xd0, value: 0, index: 0 }, 0x20); - let serial = buf.slice(0, 0x10); // serial is the wifi style serial - let secret = buf.slice(0x10, 0x10 + 10); - let hashSig = buf.slice(0x1c); + const serial = buf.slice(0, 0x10); // serial is the wifi style serial + const secret = buf.slice(0x10, 0x10 + 10); + const hashSig = buf.slice(0x1c); return [serial.toString(), secret.toString()]; } async getSerialNumber() { - var [serial, secret] = await this.getDeviceMetadata(); + const [serial, secret] = await this.getDeviceMetadata(); return serial; } async getSecret() { - var [serial, secret] = await this.getDeviceMetadata(); + const [serial, secret] = await this.getDeviceMetadata(); return secret; } async getVersion() { - let buf = await this.vendorRequest('getVersion', { + const buf = await this.vendorRequest('getVersion', { request: 0xd6, value: 0, index: 0 @@ -146,7 +138,7 @@ export default class Panda { return buf.toString(); } async getType() { - let buf = await this.vendorRequest('getType', { + const buf = await this.vendorRequest('getType', { request: 0xc1, value: 0, index: 0 @@ -184,7 +176,7 @@ export default class Panda { // i/o wrappers async vendorRequest (event, controlParams, length) { try { - let result = await this.device.vendorRequest(controlParams, length); + const result = await this.device.vendorRequest(controlParams, length); return result.data; } catch (err) { @@ -197,7 +189,7 @@ export default class Panda { message = Buffer.from([]); } try { - let result = await this.device.vendorWrite(controlParams, message); + const result = await this.device.vendorWrite(controlParams, message); return result.data; } catch (err) { @@ -225,7 +217,7 @@ export default class Panda { return this.flushEvent; } - var unlisten = raf(this.flushMessageQueue); + const unlisten = raf(this.flushMessageQueue); this.flushEvent = () => { raf.cancel(unlisten); @@ -238,7 +230,7 @@ export default class Panda { this.flushEvent(); if (this.needsFlush && this.messageQueue.length) { - let messageQueue = this.messageQueue; + const messageQueue = this.messageQueue; this.messageQueue = []; this.needsFlush = false; MessageEvent.broadcast(this, messageQueue); @@ -265,9 +257,9 @@ export default class Panda { this.isReading = true; for (let i = 0; i < MAX_MESSAGE_QUEUE; ++i) { - let data = await this.device.nextMessage(); - let receiptTime = now() / 1000 - let canMessages = unpackCAN(data); + const data = await this.device.nextMessage(); + const receiptTime = now() / 1000 + const canMessages = unpackCAN(data); if (!canMessages.length) { await wait(1); continue; diff --git a/src/test.js b/src/test.js index fdefebb..dd4faec 100644 --- a/src/test.js +++ b/src/test.js @@ -8,9 +8,9 @@ test('is valid', (t) => { }); test('manages connection state correctly, starts paused', async function(t) { - var panda = new PandaJS(); + const panda = new PandaJS(); - var usbId = await panda.connect(); + const usbId = await panda.connect(); t.ok(usbId, 'gets a USB id from connect'); t.equals(panda.isConnected(), true); t.equals(panda.isPaused(), true); @@ -23,9 +23,9 @@ test('manages connection state correctly, starts paused', async function(t) { test('re-pauses when disconnected', async function(t) { - var panda = new PandaJS(); + const panda = new PandaJS(); - var usbId = await panda.start(); + const usbId = await panda.start(); t.ok(usbId, 'gets a USB id from start'); t.equals(panda.isConnected(), true, 'is connected state after connecting'); t.equals(panda.isPaused(), false, 'starts unpaused when start is used');