@commaai/pandajs
Version:
JavaScript interface for communicating with Panda devices.
929 lines (767 loc) • 26.5 kB
JavaScript
'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;