UNPKG

@commaai/pandajs

Version:

JavaScript interface for communicating with Panda devices.

929 lines (767 loc) 26.5 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); var _regenerator = require('babel-runtime/regenerator'); var _regenerator2 = _interopRequireDefault(_regenerator); var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _canMessage = require('can-message'); var _weakmapEvent = require('weakmap-event'); var _weakmapEvent2 = _interopRequireDefault(_weakmapEvent); var _ap = require('ap'); var _performanceNow = require('performance-now'); var _performanceNow2 = _interopRequireDefault(_performanceNow); var _raf = require('raf'); var _raf2 = _interopRequireDefault(_raf); var _thyming = require('thyming'); var _delay = require('./delay'); var _delay2 = _interopRequireDefault(_delay); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // how many messages to batch at maximum when reading as fast as we can var MAX_MESSAGE_QUEUE = 5000; var MessageEvent = (0, _weakmapEvent2.default)(); var ErrorEvent = (0, _weakmapEvent2.default)(); var ConnectEvent = (0, _weakmapEvent2.default)(); var DisconnectEvent = (0, _weakmapEvent2.default)(); var Panda = function () { function Panda(options) { (0, _classCallCheck3.default)(this, Panda); // setup event handlers this.onMessage = (0, _ap.partial)(MessageEvent.listen, this); this.onError = (0, _ap.partial)(ErrorEvent.listen, this); this.onConnect = (0, _ap.partial)(ConnectEvent.listen, this); this.onDisconnect = (0, _ap.partial)(DisconnectEvent.listen, this); // initialize device object this.device = options.device; this.device.onError((0, _ap.partial)(ErrorEvent.broadcast, this)); this.device.onConnect(this.connectHandler.bind(this)); this.device.onDisconnect(this.disconnectHandler.bind(this)); // member variables this.paused = true; this.messageQueue = []; // function binding this.readLoop = this.readLoop.bind(this); this.flushMessageQueue = this.flushMessageQueue.bind(this); } // state getters (0, _createClass3.default)(Panda, [{ key: 'isConnected', value: function isConnected() { return !!this.connected; } }, { key: 'isPaused', value: function isPaused() { return !!this.paused; } // methods }, { key: 'connect', value: function () { var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() { var serialNumber; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (!this.isConnected()) { _context.next = 2; break; } return _context.abrupt('return', this.connected); case 2: _context.next = 4; return this.device.connect(); case 4: _context.next = 6; return this.getSerialNumber(); case 6: serialNumber = _context.sent; this.connectHandler(serialNumber); return _context.abrupt('return', serialNumber); case 9: case 'end': return _context.stop(); } } }, _callee, this); })); function connect() { return _ref.apply(this, arguments); } return connect; }() }, { key: 'disconnect', value: function () { var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2() { return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: if (this.isConnected()) { _context2.next = 2; break; } return _context2.abrupt('return', false); case 2: return _context2.abrupt('return', this.device.disconnect()); case 3: case 'end': return _context2.stop(); } } }, _callee2, this); })); function disconnect() { return _ref2.apply(this, arguments); } return disconnect; }() }, { key: 'start', value: function () { var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() { return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: _context3.next = 2; return this.connect(); case 2: return _context3.abrupt('return', this.unpause()); case 3: case 'end': return _context3.stop(); } } }, _callee3, this); })); function start() { return _ref3.apply(this, arguments); } return start; }() }, { key: 'pause', value: function () { var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4() { var wasPaused; return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: wasPaused = this.isPaused(); this.paused = true; return _context4.abrupt('return', !wasPaused); case 3: case 'end': return _context4.stop(); } } }, _callee4, this); })); function pause() { return _ref4.apply(this, arguments); } return pause; }() }, { key: 'resume', value: function () { var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5() { return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: return _context5.abrupt('return', this.unpause()); case 1: case 'end': return _context5.stop(); } } }, _callee5, this); })); function resume() { return _ref5.apply(this, arguments); } return resume; }() }, { key: 'unpause', value: function () { var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6() { var wasPaused; return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: wasPaused = this.isPaused(); if (wasPaused) { _context6.next = 3; break; } return _context6.abrupt('return', false); case 3: this.paused = false; this.startReading(); return _context6.abrupt('return', wasPaused); case 6: case 'end': return _context6.stop(); } } }, _callee6, this); })); function unpause() { return _ref6.apply(this, arguments); } return unpause; }() // vendor API methods }, { key: 'getHealth', value: function () { var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7() { var buf, voltage, current, isStarted, controlsAreAllowed, isGasInterceptorDetector, isStartSignalDetected, isStartedAlt; return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: _context7.next = 2; return this.vendorRequest('health', { request: 0xd2, value: 0, index: 0 }, 13); case 2: buf = _context7.sent; 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; return _context7.abrupt('return', { voltage: voltage, current: current, isStarted: isStarted, controlsAreAllowed: controlsAreAllowed, isGasInterceptorDetector: isGasInterceptorDetector, isStartSignalDetected: isStartSignalDetected, isStartedAlt: isStartedAlt }); case 11: case 'end': return _context7.stop(); } } }, _callee7, this); })); function getHealth() { return _ref7.apply(this, arguments); } return getHealth; }() }, { key: 'getDeviceMetadata', value: function () { var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8() { var buf, serial, secret, hashSig; return _regenerator2.default.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: _context8.next = 2; return this.vendorRequest('getDeviceMetadata', { request: 0xd0, value: 0, index: 0 }, 0x20); case 2: buf = _context8.sent; serial = buf.slice(0, 0x10); // serial is the wifi style serial secret = buf.slice(0x10, 0x10 + 10); hashSig = buf.slice(0x1c); return _context8.abrupt('return', [serial.toString(), secret.toString()]); case 7: case 'end': return _context8.stop(); } } }, _callee8, this); })); function getDeviceMetadata() { return _ref8.apply(this, arguments); } return getDeviceMetadata; }() }, { key: 'getSerialNumber', value: function () { var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9() { var _ref10, _ref11, serial, secret; return _regenerator2.default.wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { case 0: _context9.next = 2; return this.getDeviceMetadata(); case 2: _ref10 = _context9.sent; _ref11 = (0, _slicedToArray3.default)(_ref10, 2); serial = _ref11[0]; secret = _ref11[1]; return _context9.abrupt('return', serial); case 7: case 'end': return _context9.stop(); } } }, _callee9, this); })); function getSerialNumber() { return _ref9.apply(this, arguments); } return getSerialNumber; }() }, { key: 'getSecret', value: function () { var _ref12 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10() { var _ref13, _ref14, serial, secret; return _regenerator2.default.wrap(function _callee10$(_context10) { while (1) { switch (_context10.prev = _context10.next) { case 0: _context10.next = 2; return this.getDeviceMetadata(); case 2: _ref13 = _context10.sent; _ref14 = (0, _slicedToArray3.default)(_ref13, 2); serial = _ref14[0]; secret = _ref14[1]; return _context10.abrupt('return', secret); case 7: case 'end': return _context10.stop(); } } }, _callee10, this); })); function getSecret() { return _ref12.apply(this, arguments); } return getSecret; }() }, { key: 'getVersion', value: function () { var _ref15 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee11() { var buf; return _regenerator2.default.wrap(function _callee11$(_context11) { while (1) { switch (_context11.prev = _context11.next) { case 0: _context11.next = 2; return this.vendorRequest('getVersion', { request: 0xd6, value: 0, index: 0 }, 0x40); case 2: buf = _context11.sent; return _context11.abrupt('return', buf.toString()); case 4: case 'end': return _context11.stop(); } } }, _callee11, this); })); function getVersion() { return _ref15.apply(this, arguments); } return getVersion; }() }, { key: 'getType', value: function () { var _ref16 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee12() { var buf; return _regenerator2.default.wrap(function _callee12$(_context12) { while (1) { switch (_context12.prev = _context12.next) { case 0: _context12.next = 2; return this.vendorRequest('getType', { request: 0xc1, value: 0, index: 0 }, 0x40); case 2: buf = _context12.sent; return _context12.abrupt('return', buf.length ? buf[0] : null); case 4: case 'end': return _context12.stop(); } } }, _callee12, this); })); function getType() { return _ref16.apply(this, arguments); } return getType; }() }, { key: 'isWhite', value: function () { var _ref17 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee13() { return _regenerator2.default.wrap(function _callee13$(_context13) { while (1) { switch (_context13.prev = _context13.next) { case 0: _context13.next = 2; return this.getType(); case 2: _context13.t0 = _context13.sent; return _context13.abrupt('return', _context13.t0 === 1); case 4: case 'end': return _context13.stop(); } } }, _callee13, this); })); function isWhite() { return _ref17.apply(this, arguments); } return isWhite; }() }, { key: 'isGrey', value: function () { var _ref18 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee14() { return _regenerator2.default.wrap(function _callee14$(_context14) { while (1) { switch (_context14.prev = _context14.next) { case 0: _context14.next = 2; return this.getType(); case 2: _context14.t0 = _context14.sent; return _context14.abrupt('return', _context14.t0 === 2); case 4: case 'end': return _context14.stop(); } } }, _callee14, this); })); function isGrey() { return _ref18.apply(this, arguments); } return isGrey; }() }, { key: 'isBlack', value: function () { var _ref19 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee15() { return _regenerator2.default.wrap(function _callee15$(_context15) { while (1) { switch (_context15.prev = _context15.next) { case 0: _context15.next = 2; return this.getType(); case 2: _context15.t0 = _context15.sent; return _context15.abrupt('return', _context15.t0 === 3); case 4: case 'end': return _context15.stop(); } } }, _callee15, this); })); function isBlack() { return _ref19.apply(this, arguments); } return isBlack; }() }, { key: 'hasObd', value: function () { var _ref20 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee16() { return _regenerator2.default.wrap(function _callee16$(_context16) { while (1) { switch (_context16.prev = _context16.next) { case 0: _context16.next = 2; return this.getType(); case 2: _context16.t0 = _context16.sent; return _context16.abrupt('return', _context16.t0 > 2); case 4: case 'end': return _context16.stop(); } } }, _callee16, this); })); function hasObd() { return _ref20.apply(this, arguments); } return hasObd; }() }, { key: 'setObd', value: function () { var _ref21 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee17(obd) { return _regenerator2.default.wrap(function _callee17$(_context17) { while (1) { switch (_context17.prev = _context17.next) { case 0: _context17.next = 2; return this.vendorWrite('setObd', { request: 0xdb, value: obd ? 1 : 0, index: 0 }); case 2: case 'end': return _context17.stop(); } } }, _callee17, this); })); function setObd(_x) { return _ref21.apply(this, arguments); } return setObd; }() }, { key: 'setSafetyMode', value: function () { var _ref22 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee18(mode) { return _regenerator2.default.wrap(function _callee18$(_context18) { while (1) { switch (_context18.prev = _context18.next) { case 0: _context18.next = 2; return this.vendorWrite('setSafetyMode', { request: 0xdc, value: mode, index: 0 }); case 2: case 'end': return _context18.stop(); } } }, _callee18, this); })); function setSafetyMode(_x2) { return _ref22.apply(this, arguments); } return setSafetyMode; }() // i/o wrappers }, { key: 'vendorRequest', value: function () { var _ref23 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee19(event, controlParams, length) { var result; return _regenerator2.default.wrap(function _callee19$(_context19) { while (1) { switch (_context19.prev = _context19.next) { case 0: _context19.prev = 0; _context19.next = 3; return this.device.vendorRequest(controlParams, length); case 3: result = _context19.sent; return _context19.abrupt('return', result.data); case 7: _context19.prev = 7; _context19.t0 = _context19['catch'](0); ErrorEvent.broadcast(this, { event: 'Panda.' + event + ' failed', error: _context19.t0 }); throw _context19.t0; case 11: case 'end': return _context19.stop(); } } }, _callee19, this, [[0, 7]]); })); function vendorRequest(_x3, _x4, _x5) { return _ref23.apply(this, arguments); } return vendorRequest; }() }, { key: 'vendorWrite', value: function () { var _ref24 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee20(event, controlParams, message) { var result; return _regenerator2.default.wrap(function _callee20$(_context20) { while (1) { switch (_context20.prev = _context20.next) { case 0: if (!message || !message.length) { message = Buffer.from([]); } _context20.prev = 1; _context20.next = 4; return this.device.vendorWrite(controlParams, message); case 4: result = _context20.sent; return _context20.abrupt('return', result.data); case 8: _context20.prev = 8; _context20.t0 = _context20['catch'](1); ErrorEvent.broadcast(this, { event: 'Panda.' + event + ' failed', error: _context20.t0 }); throw _context20.t0; case 12: case 'end': return _context20.stop(); } } }, _callee20, this, [[1, 8]]); })); function vendorWrite(_x6, _x7, _x8) { return _ref24.apply(this, arguments); } return vendorWrite; }() // event handlers }, { key: 'connectHandler', value: function connectHandler(usbId) { this.connected = usbId; ConnectEvent.broadcast(this, usbId); } }, { key: 'disconnectHandler', value: function disconnectHandler() { var previousConnection = this.connected; this.connected = false; this.paused = true; DisconnectEvent.broadcast(this, previousConnection); } // message queueing and flushing }, { key: 'needsFlushMessageQueue', value: function needsFlushMessageQueue() { var _this = this; this.needsFlush = true; if (this.flushEvent) { return this.flushEvent; } var unlisten = (0, _raf2.default)(this.flushMessageQueue); this.flushEvent = function () { _raf2.default.cancel(unlisten); _this.flushEvent = false; }; return this.flushEvent; } }, { key: 'flushMessageQueue', value: function flushMessageQueue() { this.flushEvent(); if (this.needsFlush && this.messageQueue.length) { var messageQueue = this.messageQueue; this.messageQueue = []; this.needsFlush = false; MessageEvent.broadcast(this, messageQueue); } } // internal reading loop }, { key: 'startReading', value: function startReading() { if (this.isReading) { return true; } if (this.isPaused()) { return false; } // start loop! this.isReading = true; this.readLoop(); } }, { key: 'readLoop', value: function () { var _ref25 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee21() { var i, data, receiptTime, canMessages; return _regenerator2.default.wrap(function _callee21$(_context21) { while (1) { switch (_context21.prev = _context21.next) { case 0: if (!this.isPaused()) { _context21.next = 3; break; } this.isReading = false; return _context21.abrupt('return', false); case 3: this.isReading = true; i = 0; case 5: if (!(i < MAX_MESSAGE_QUEUE)) { _context21.next = 20; break; } _context21.next = 8; return this.device.nextMessage(); case 8: data = _context21.sent; receiptTime = (0, _performanceNow2.default)() / 1000; canMessages = (0, _canMessage.unpackCAN)(data); if (canMessages.length) { _context21.next = 15; break; } _context21.next = 14; return (0, _delay2.default)(1); case 14: return _context21.abrupt('continue', 17); case 15: this.messageQueue.push({ time: receiptTime, canMessages: canMessages }); this.needsFlushMessageQueue(); case 17: ++i; _context21.next = 5; break; case 20: this.needsFlushMessageQueue(); // repeat! (0, _thyming.timeout)(this.readLoop); case 22: case 'end': return _context21.stop(); } } }, _callee21, this); })); function readLoop() { return _ref25.apply(this, arguments); } return readLoop; }() }]); return Panda; }(); exports.default = Panda;