UNPKG

@commaai/pandajs

Version:

JavaScript interface for communicating with Panda devices.

525 lines (425 loc) 15.4 kB
'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 _delay = require('../delay'); var _delay2 = _interopRequireDefault(_delay); var _isPromise = require('is-promise'); var _isPromise2 = _interopRequireDefault(_isPromise); var _net = require('net'); var _net2 = _interopRequireDefault(_net); var _dgram = require('dgram'); var _dgram2 = _interopRequireDefault(_dgram); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var PANDA_MESSAGE_ENDPOINT_NUMBER = 1; var PANDA_HOST = '192.168.0.10'; var PANDA_TCP_PORT = 1337; var PANDA_UDP_PORT = 1338; var REQUEST_OUT = 64; var REQUEST_IN = 192; var ErrorEvent = (0, _weakmapEvent2.default)(); var ConnectEvent = (0, _weakmapEvent2.default)(); var DisconnectEvent = (0, _weakmapEvent2.default)(); var DataEvent = (0, _weakmapEvent2.default)(); var MessageEvent = (0, _weakmapEvent2.default)(); var Panda = function () { function Panda(options) { (0, _classCallCheck3.default)(this, Panda); this.device = null; this.onError = (0, _ap.partial)(ErrorEvent.listen, this); this.onConnect = (0, _ap.partial)(ConnectEvent.listen, this); this.onDisconnect = (0, _ap.partial)(DisconnectEvent.listen, this); this.handleData = this.handleData.bind(this); this.handleError = this.handleError.bind(this); } (0, _createClass3.default)(Panda, [{ key: 'connectToTCP', value: function () { var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() { var _this = this; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: return _context.abrupt('return', new Promise(function (resolve, reject) { var fail = function fail(err) { _this.socket = null; ErrorEvent.broadcast(_this, err); reject(err); }; var succeed = function succeed() { _this.socket.off('close', fail); _this.socket.off('error', fail); resolve(); }; _this.socket = _net2.default.connect(PANDA_TCP_PORT, PANDA_HOST); _this.socket.on('connect', resolve); _this.socket.on('close', fail); _this.socket.on('error', fail); })); case 1: case 'end': return _context.stop(); } } }, _callee, this); })); function connectToTCP() { return _ref.apply(this, arguments); } return connectToTCP; }() }, { key: 'connect', 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: this.ignoreLengths = {}; _context2.next = 3; return this.connectToTCP(); case 3: this.socket.on('data', this.handleData); this.socket.on('close', (0, _ap.partial)(DisconnectEvent.broadcast, this)); this.socket.on('error', this.handleError); return _context2.abrupt('return', true); case 7: case 'end': return _context2.stop(); } } }, _callee2, this); })); function connect() { return _ref2.apply(this, arguments); } return connect; }() }, { key: 'disconnect', 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: if (this.socket) { _context3.next = 2; break; } return _context3.abrupt('return', false); case 2: _context3.next = 4; return this.socket.close(); case 4: this.socket = null; return _context3.abrupt('return', true); case 6: case 'end': return _context3.stop(); } } }, _callee3, this); })); function disconnect() { return _ref3.apply(this, arguments); } return disconnect; }() }, { key: 'vendorRequest', value: function () { var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(data, length) { return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: _context4.next = 2; return this.controlRead(REQUEST_OUT, data.request, data.value, data.index, length); case 2: data = _context4.sent; return _context4.abrupt('return', { data: Buffer.from(data), status: "ok" // hack, find out when it's actually ok }); case 4: case 'end': return _context4.stop(); } } }, _callee4, this); })); function vendorRequest(_x, _x2) { return _ref4.apply(this, arguments); } return vendorRequest; }() // not used anymore, but is nice for reference }, { key: 'nextFakeMessage', 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 (0, _delay2.default)(10); case 2: return _context5.abrupt('return', (0, _canMessage.packCAN)({ address: 0, busTime: ~~(Math.random() * 65000), data: ''.padEnd(16, '0'), bus: 0 })); case 3: case 'end': return _context5.stop(); } } }, _callee5, this); })); function nextFakeMessage() { return _ref5.apply(this, arguments); } return nextFakeMessage; }() }, { key: 'controlRead', value: function () { var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(requestType, request, value, index, length) { var buf; return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: if (!this.ignoreLengths[length]) { this.ignoreLengths[length] = 0; } this.ignoreLengths[length]++; 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 _context6.abrupt('return', this.nextIncomingData()); case 12: case 'end': return _context6.stop(); } } }, _callee6, this); })); function controlRead(_x3, _x4, _x5, _x6, _x7) { return _ref6.apply(this, arguments); } return controlRead; }() }, { key: 'nextIncomingData', value: function () { var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7() { var _this2 = 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) { once((0, _ap.partial)(DataEvent.listen, _this2), resolve); })); case 1: case 'end': return _context7.stop(); } } }, _callee7, this); })); function nextIncomingData() { return _ref7.apply(this, arguments); } return nextIncomingData; }() }, { key: 'nextIncomingMessage', value: function () { var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8() { var _this3 = 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) { once((0, _ap.partial)(MessageEvent.listen, _this3), resolve); })); case 1: case 'end': return _context8.stop(); } } }, _callee8, this); })); function nextIncomingMessage() { return _ref8.apply(this, arguments); } return nextIncomingMessage; }() }, { key: 'nextMessage', value: function () { var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9() { var result, attempts; return _regenerator2.default.wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { case 0: result = null; attempts = 0; case 2: if (!(result === null)) { _context9.next = 17; break; } _context9.prev = 3; _context9.next = 6; return this.bulkRead(1); case 6: return _context9.abrupt('return', _context9.sent); case 9: _context9.prev = 9; _context9.t0 = _context9['catch'](3); console.warn('can_recv failed, retrying'); attempts = Math.min(++attempts, 10); _context9.next = 15; return (0, _delay2.default)(attempts * 100); case 15: _context9.next = 2; break; case 17: case 'end': return _context9.stop(); } } }, _callee9, this, [[3, 9]]); })); function nextMessage() { return _ref9.apply(this, arguments); } return nextMessage; }() }, { key: 'handleData', value: function () { var _ref10 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10(buf) { var length, data; return _regenerator2.default.wrap(function _callee10$(_context10) { while (1) { switch (_context10.prev = _context10.next) { case 0: length = buf.readUInt32LE(0); 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); } case 3: case 'end': return _context10.stop(); } } }, _callee10, this); })); function handleData(_x8) { return _ref10.apply(this, arguments); } return handleData; }() }, { key: 'handleError', value: function () { var _ref11 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee11(err) { return _regenerator2.default.wrap(function _callee11$(_context11) { while (1) { switch (_context11.prev = _context11.next) { case 0: if (err.errno === 'ETIMEDOUT') { DisconnectEvent.broadcast(this, err.errno); } else { ErrorEvent.broadcast(this, err); } case 1: case 'end': return _context11.stop(); } } }, _callee11, this); })); function handleError(_x9) { return _ref11.apply(this, arguments); } return handleError; }() }, { key: 'bulkRead', value: function () { var _ref12 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee12(endpoint) { var timeoutMillis = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var promise, buf; return _regenerator2.default.wrap(function _callee12$(_context12) { while (1) { switch (_context12.prev = _context12.next) { case 0: promise = this.nextIncomingMessage(); buf = Buffer.alloc(4); buf.writeUInt16LE(endpoint, 0); buf.writeUInt16LE(0, 2); this.socket.write(buf); return _context12.abrupt('return', promise); case 6: case 'end': return _context12.stop(); } } }, _callee12, this); })); function bulkRead(_x11) { return _ref12.apply(this, arguments); } return bulkRead; }() }]); return Panda; }(); exports.default = Panda; function once(event, handler) { var unlisten = event(onceHandler); return unlisten; function onceHandler() { unlisten(); handler.apply(this, arguments); } }