@commaai/pandajs
Version:
JavaScript interface for communicating with Panda devices.
587 lines (474 loc) • 18.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
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 _usb = require('usb');
var _usb2 = _interopRequireDefault(_usb);
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 _delay = require('../delay');
var _delay2 = _interopRequireDefault(_delay);
var _isPromise = require('is-promise');
var _isPromise2 = _interopRequireDefault(_isPromise);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var PANDA_VENDOR_ID = 0xbbaa;
var PANDA_MESSAGE_ENDPOINT_NUMBER = 1;
// const PANDA_PRODUCT_ID = 0xddcc;
var BUFFER_SIZE = 0x10 * 256;
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);
this.device = null;
this.selectDeviceMethod = options.selectDevice;
this.onError = (0, _ap.partial)(ErrorEvent.listen, this);
this.onConnect = (0, _ap.partial)(ConnectEvent.listen, this);
this.onDisconnect = (0, _ap.partial)(DisconnectEvent.listen, this);
}
(0, _createClass3.default)(Panda, [{
key: 'findDevice',
value: function () {
var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
var devices;
return _regenerator2.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
devices = _usb2.default.getDeviceList();
devices = devices.filter(function (device) {
return device.deviceDescriptor.idVendor === PANDA_VENDOR_ID;
});
return _context.abrupt('return', this.selectDevice(devices));
case 3:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
function findDevice() {
return _ref.apply(this, arguments);
}
return findDevice;
}()
}, {
key: 'selectDevice',
value: function () {
var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(devices) {
var _this = this;
return _regenerator2.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
return _context2.abrupt('return', new Promise(function (resolve, reject) {
var result = _this.selectDeviceMethod(devices, resolve);
if (result) {
if ((0, _isPromise2.default)(result)) {
result.then(resolve).catch(reject);
} else {
resolve(result);
}
}
}));
case 1:
case 'end':
return _context2.stop();
}
}
}, _callee2, this);
}));
function selectDevice(_x) {
return _ref2.apply(this, arguments);
}
return selectDevice;
}()
}, {
key: 'setConfiguration',
value: function () {
var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(config) {
var _this2 = this;
return _regenerator2.default.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
return _context3.abrupt('return', new Promise(function (resolve, reject) {
_this2.device.setConfiguration(1, function (err, result) {
if (err) {
return reject(err);
}
resolve(result);
});
}));
case 1:
case 'end':
return _context3.stop();
}
}
}, _callee3, this);
}));
function setConfiguration(_x2) {
return _ref3.apply(this, arguments);
}
return setConfiguration;
}()
}, {
key: 'getStringDescriptor',
value: function () {
var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(index) {
var _this3 = this;
return _regenerator2.default.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
return _context4.abrupt('return', new Promise(function (resolve, reject) {
_this3.device.getStringDescriptor(index, function (err, buffer) {
if (err) {
return reject(err);
}
resolve(buffer.toString());
});
}));
case 1:
case 'end':
return _context4.stop();
}
}
}, _callee4, this);
}));
function getStringDescriptor(_x3) {
return _ref4.apply(this, arguments);
}
return getStringDescriptor;
}()
}, {
key: 'connect',
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:
_context5.next = 2;
return this.findDevice();
case 2:
this.device = _context5.sent;
_context5.next = 5;
return this.device.open(false);
case 5:
_context5.next = 7;
return this.setConfiguration(1);
case 7:
_context5.next = 9;
return this.device.interface(0).claim();
case 9:
return _context5.abrupt('return', true);
case 10:
case 'end':
return _context5.stop();
}
}
}, _callee5, this);
}));
function connect() {
return _ref5.apply(this, arguments);
}
return connect;
}()
}, {
key: 'disconnect',
value: function () {
var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6() {
return _regenerator2.default.wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
if (this.device) {
_context6.next = 2;
break;
}
return _context6.abrupt('return', false);
case 2:
_context6.next = 4;
return this.device.close();
case 4:
this.device = null;
return _context6.abrupt('return', true);
case 6:
case 'end':
return _context6.stop();
}
}
}, _callee6, this);
}));
function disconnect() {
return _ref6.apply(this, arguments);
}
return disconnect;
}()
}, {
key: 'vendorRequest',
value: function () {
var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(controlParams, length) {
var _this4 = this;
return _regenerator2.default.wrap(function _callee7$(_context7) {
while (1) {
switch (_context7.prev = _context7.next) {
case 0:
return _context7.abrupt('return', new Promise(function (resolve, reject) {
// controlParams is request, value, index
var flags = _usb2.default.LIBUSB_RECIPIENT_DEVICE | _usb2.default.LIBUSB_REQUEST_TYPE_VENDOR | _usb2.default.LIBUSB_ENDPOINT_IN;
_this4.device.controlTransfer(flags, controlParams.request, controlParams.value, controlParams.index, length, function (err, data) {
if (err) {
return reject(err);
}
resolve({
data: Buffer.from(data),
status: "ok" // hack, find out when it's actually ok
});
});
}));
case 1:
case 'end':
return _context7.stop();
}
}
}, _callee7, this);
}));
function vendorRequest(_x4, _x5) {
return _ref7.apply(this, arguments);
}
return vendorRequest;
}()
}, {
key: 'vendorWrite',
value: function () {
var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8(controlParams, message) {
var _this5 = this;
return _regenerator2.default.wrap(function _callee8$(_context8) {
while (1) {
switch (_context8.prev = _context8.next) {
case 0:
return _context8.abrupt('return', new Promise(function (resolve, reject) {
// controlParams is request, value, index
var flags = _usb2.default.LIBUSB_RECIPIENT_DEVICE | _usb2.default.LIBUSB_REQUEST_TYPE_VENDOR | _usb2.default.LIBUSB_ENDPOINT_OUT;
_this5.device.controlTransfer(flags, controlParams.request, controlParams.value, controlParams.index, message, function (err, data) {
if (err) {
return reject(err);
}
resolve({
data: Buffer.from([]),
status: "ok" // hack, find out when it's actually ok
});
});
}));
case 1:
case 'end':
return _context8.stop();
}
}
}, _callee8, this);
}));
function vendorWrite(_x6, _x7) {
return _ref8.apply(this, arguments);
}
return vendorWrite;
}()
// not used anymore, but is nice for reference
}, {
key: 'nextFakeMessage',
value: function () {
var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9() {
return _regenerator2.default.wrap(function _callee9$(_context9) {
while (1) {
switch (_context9.prev = _context9.next) {
case 0:
_context9.next = 2;
return (0, _delay2.default)(10);
case 2:
return _context9.abrupt('return', (0, _canMessage.packCAN)({
address: 0,
busTime: ~~(Math.random() * 65000),
data: ''.padEnd(16, '0'),
bus: 0
}));
case 3:
case 'end':
return _context9.stop();
}
}
}, _callee9, this);
}));
function nextFakeMessage() {
return _ref9.apply(this, arguments);
}
return nextFakeMessage;
}()
}, {
key: 'transferIn',
value: function () {
var _ref10 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee11(endpointNumber, length) {
var _this6 = this;
return _regenerator2.default.wrap(function _callee11$(_context11) {
while (1) {
switch (_context11.prev = _context11.next) {
case 0:
// in endpoints are on another address scope, so we or in 0x80 to get 0x81
endpointNumber = endpointNumber | 0x80;
return _context11.abrupt('return', new Promise(function () {
var _ref11 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10(resolve, reject) {
var endpoint, err, _err, data;
return _regenerator2.default.wrap(function _callee10$(_context10) {
while (1) {
switch (_context10.prev = _context10.next) {
case 0:
endpoint = null;
_this6.device.interfaces.some(function (iface) {
var epoint = iface.endpoint(endpointNumber);
if (epoint) {
endpoint = epoint;
return true;
}
});
if (endpoint) {
_context10.next = 6;
break;
}
err = new Error('PandaJS: nodeusb: transferIn failed to find endpoint interface ' + endpointNumber);
ErrorEvent.broadcast(_this6, err);
return _context10.abrupt('return', reject(err));
case 6:
if (!(endpoint.direction !== 'in')) {
_context10.next = 10;
break;
}
_err = new Error('PandaJS: nodeusb: endpoint interface is ' + endpoint.direction + ' instead of in');
ErrorEvent.broadcast(_this6, _err);
return _context10.abrupt('return', reject(_err));
case 10:
data = Buffer.from([]);
case 11:
if (!(data.length === 0)) {
_context10.next = 17;
break;
}
_context10.next = 14;
return _this6.endpointTransfer(endpoint, length);
case 14:
data = _context10.sent;
_context10.next = 11;
break;
case 17:
resolve(data);
case 18:
case 'end':
return _context10.stop();
}
}
}, _callee10, _this6);
}));
return function (_x10, _x11) {
return _ref11.apply(this, arguments);
};
}()));
case 2:
case 'end':
return _context11.stop();
}
}
}, _callee11, this);
}));
function transferIn(_x8, _x9) {
return _ref10.apply(this, arguments);
}
return transferIn;
}()
}, {
key: 'endpointTransfer',
value: function () {
var _ref12 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee12(endpoint, length) {
return _regenerator2.default.wrap(function _callee12$(_context12) {
while (1) {
switch (_context12.prev = _context12.next) {
case 0:
return _context12.abrupt('return', new Promise(function (resolve, reject) {
endpoint.transfer(length, function (err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
}));
case 1:
case 'end':
return _context12.stop();
}
}
}, _callee12, this);
}));
function endpointTransfer(_x12, _x13) {
return _ref12.apply(this, arguments);
}
return endpointTransfer;
}()
}, {
key: 'nextMessage',
value: function () {
var _ref13 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee13() {
var result, attempts;
return _regenerator2.default.wrap(function _callee13$(_context13) {
while (1) {
switch (_context13.prev = _context13.next) {
case 0:
result = null;
attempts = 0;
case 2:
if (!(result === null)) {
_context13.next = 18;
break;
}
_context13.prev = 3;
_context13.next = 6;
return this.transferIn(1, BUFFER_SIZE);
case 6:
return _context13.abrupt('return', _context13.sent);
case 9:
_context13.prev = 9;
_context13.t0 = _context13['catch'](3);
console.log(_context13.t0);
console.warn('can_recv failed, retrying');
attempts = Math.min(++attempts, 10);
_context13.next = 16;
return (0, _delay2.default)(attempts * 100);
case 16:
_context13.next = 2;
break;
case 18:
case 'end':
return _context13.stop();
}
}
}, _callee13, this, [[3, 9]]);
}));
function nextMessage() {
return _ref13.apply(this, arguments);
}
return nextMessage;
}()
}]);
return Panda;
}();
exports.default = Panda;