diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4a7ea30 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/package.json b/package.json index 2b2686d..db6eab9 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "homepage": "https://community.comma.ai/cabana", "dependencies": { + "@commaai/pandajs": "^0.1.0", "aphrodite": "^1.2.1", "babel-preset-stage-0": "^6.24.1", "base64-inline-loader": "^1.1.0", @@ -46,7 +47,8 @@ "socket.io-client": "^2.0.3", "streamsaver": "^1.0.1", "vega": "3.0.0", - "vega-tooltip": "^0.4.0" + "vega-tooltip": "^0.4.0", + "xtend": "^4.0.1" }, "devDependencies": { "connect-history-api-fallback": "1.3.0", diff --git a/src/CanExplorer.js b/src/CanExplorer.js index 4028379..3952bc2 100644 --- a/src/CanExplorer.js +++ b/src/CanExplorer.js @@ -1,11 +1,12 @@ import React, { Component } from "react"; import Moment from "moment"; import PropTypes from "prop-types"; +import cx from "classnames"; +import { createWriteStream } from "streamsaver"; +import Panda from "@commaai/pandajs"; import { USE_UNLOGGER, PART_SEGMENT_LENGTH, STREAMING_WINDOW } from "./config"; import * as GithubAuth from "./api/github-auth"; -import cx from "classnames"; -import { createWriteStream } from "streamsaver"; import auth from "./api/comma-auth"; import DBC from "./models/can/dbc"; @@ -25,7 +26,6 @@ import { } from "./api/localstorage"; import OpenDbc from "./api/OpenDbc"; import UnloggerClient from "./api/unlogger"; -import PandaReader from "./api/panda-reader"; import * as ObjectUtils from "./utils/object"; import { hash } from "./utils/string"; @@ -86,8 +86,6 @@ export default class CanExplorer extends Component { this.unloggerClient = new UnloggerClient(); } - this.pandaReader = new PandaReader(); - this.showOnboarding = this.showOnboarding.bind(this); this.hideOnboarding = this.hideOnboarding.bind(this); this.showLoadDbc = this.showLoadDbc.bind(this); @@ -118,6 +116,9 @@ export default class CanExplorer extends Component { this.lastMessageEntriesById = this.lastMessageEntriesById.bind(this); this.githubSignOut = this.githubSignOut.bind(this); this.downloadLogAsCSV = this.downloadLogAsCSV.bind(this); + + this.pandaReader = new Panda(); + this.pandaReader.onMessage(this.processStreamedCanMessages); } componentWillMount() { @@ -754,34 +755,33 @@ export default class CanExplorer extends Component { this._onStreamedCanMessagesProcessed(e.data); } - handlePandaConnect(e) { + async handlePandaConnect(e) { this.setState({ attemptingPandaConnection: true, live: true }); const persistedDbc = fetchPersistedDbc("live"); if (persistedDbc) { - const { dbc, dbcText } = persistedDbc; + let { dbc, dbcText } = persistedDbc; this.setState({ dbc, dbcText }); } this.canStreamerWorker = new CanStreamerWorker(); this.canStreamerWorker.onmessage = this.onStreamedCanMessagesProcessed; - this.pandaReader.setOnMessagesReceivedCallback( - this.processStreamedCanMessages + + // if any errors go off during connection, mark as not trying to connect anymore... + let unlisten = this.pandaReader.onError(err => + this.setState({ attemptingPandaConnection: false }) ); - this.pandaReader - .connect() - .then(() => { - this.pandaReader.readLoop(); - this.setState({ attemptingPandaConnection: false }); - this.setState({ showOnboarding: false }); - this.setState({ showLoadDbc: true }); - }) - .catch(err => { - console.log(err); - this.setState({ attemptingPandaConnection: false }); + try { + await this.pandaReader.start(); + this.setState({ + showOnboarding: false, + showLoadDbc: true }); + } catch (e) {} + this.setState({ attemptingPandaConnection: false }); + unlisten(); } - githubSignOut(e) { + githubSignOut(e, dataArray) { unpersistGithubAuthToken(); this.setState({ isGithubAuthenticated: false }); diff --git a/src/__tests__/panda/panda.test.js b/src/__tests__/panda/panda.test.js deleted file mode 100644 index 1641a01..0000000 --- a/src/__tests__/panda/panda.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import Panda from "../../api/panda"; - -function arrayBufferFromHex(hex) { - const buffer = Buffer.from(hex, "hex"); - const arrayBuffer = new ArrayBuffer(buffer.length); - const view = new Uint8Array(arrayBuffer); - for (let i = 0; i < buffer.length; i++) { - view[i] = buffer[i]; - } - return arrayBuffer; -} - -test("parseCanBuffer correctly parses a message", () => { - const panda = new Panda(); - // 16 byte buffer - - const arrayBuffer = arrayBufferFromHex("abababababababababababababababab"); - - const messages = panda.parseCanBuffer(arrayBuffer); - expect(messages.length).toEqual(1); - expect(messages[0]).toEqual([1373, 43947, "abababababababab", 10]); -}); diff --git a/src/api/panda-reader.js b/src/api/panda-reader.js index ee94e7a..01427c4 100644 --- a/src/api/panda-reader.js +++ b/src/api/panda-reader.js @@ -1,10 +1,16 @@ -import Panda from "./panda"; +import CloudLog from "../logging/CloudLog"; +import Panda from "pandajs"; + +///@TODO move this file into pandajs and fix up the API +// it should handle read loops, event management, pausing, unpausing, etc... export default class PandaReader { static ERROR_NO_DEVICE_SELECTED = 8; constructor() { this.panda = new Panda(); + this.panda.onError(CloudLog.error.bind(CloudLog)); + this.isReading = false; this.onMessagesReceived = () => {}; this.callbackQueue = []; diff --git a/src/api/panda.js b/src/api/panda.js deleted file mode 100644 index 92d3154..0000000 --- a/src/api/panda.js +++ /dev/null @@ -1,110 +0,0 @@ -import CloudLog from "../logging/CloudLog"; - -const PANDA_VENDOR_ID = 0xbbaa; -//const PANDA_PRODUCT_ID = 0xddcc; - -const BUFFER_SIZE = 0x10 * 256; - -export default class Panda { - constructor() { - this.device = null; - } - - connect() { - // Must be called via a mouse click handler, per Chrome restrictions. - return navigator.usb - .requestDevice({ filters: [{ vendorId: PANDA_VENDOR_ID }] }) - .then(device => { - this.device = device; - return device.open(); - }) - .then(() => this.device.selectConfiguration(1)) - .then(() => this.device.claimInterface(0)); - } - - async health() { - const controlParams = { - requestType: "vendor", - recipient: "device", - request: 0xd2, - value: 0, - index: 0 - }; - try { - return await this.device.controlTransferIn(controlParams, 13); - } catch (err) { - CloudLog.error({ event: "Panda.health failed", error: err }); - } - } - - parseCanBuffer(buffer) { - const messages = []; - - for (let i = 0; i < buffer.byteLength; i += 0x10) { - const dat = buffer.slice(i, i + 0x10); - - const datView = Buffer.from(dat); - const f1 = datView.readInt32LE(0), - f2 = datView.readInt32LE(4); - - const address = f1 >>> 21; - - const busTime = f2 >>> 16; - const data = new Buffer(dat.slice(8, 8 + (f2 & 0xf))); - const source = (f2 >> 4) & 0xf & 0xff; - - messages.push([ - address, - busTime, - data.toString("hex").padEnd(16, "0"), - source - ]); - } - - return messages; - } - - async mockCanRecv() { - const promise = new Promise(resolve => - setTimeout( - () => - resolve({ - time: performance.now() / 1000, - canMessages: [[0, Math.random() * 65000, "".padEnd(16, "0"), 0]] - }), - 100 - ) - ); - return await promise; - } - - async canRecv() { - let result = null, - receiptTime = null, - attempts = 0; - - while (result === null) { - try { - result = await this.device.transferIn(1, BUFFER_SIZE); - receiptTime = performance.now() / 1000; - } catch (err) { - console.warn("can_recv failed, retrying"); - attempts = Math.min(++attempts, 10); - await wait(attempts * 100); - } - } - - await wait(0); - - return { - time: receiptTime, - canMessages: this.parseCanBuffer(result.data.buffer) - }; - } -} - -async function wait(ms) { - return new Promise(function(resolve, reject) { - setTimeout(resolve, ms); - }); -} diff --git a/src/utils/dbc.js b/src/utils/dbc.js index 11ea62b..f9da23d 100644 --- a/src/utils/dbc.js +++ b/src/utils/dbc.js @@ -6,17 +6,18 @@ function findMaxByteStateChangeCount(messages) { } function addCanMessage( - [address, busTime, data, source], + canMessage, dbc, canStartTime, messages, prevMsgEntries, byteStateChangeCountsByMessage ) { - var id = source + ":" + address.toString(16); + var { address, busTime, data, bus } = canMessage; + var id = bus + ":" + address.toString(16); if (messages[id] === undefined) - messages[id] = createMessageSpec(dbc, address, id, source); + messages[id] = createMessageSpec(dbc, address, id, bus); const prevMsgEntry = messages[id].entries.length > 0 diff --git a/src/workers/CanStreamerWorker.worker.js b/src/workers/CanStreamerWorker.worker.js index 4593469..3377e1c 100644 --- a/src/workers/CanStreamerWorker.worker.js +++ b/src/workers/CanStreamerWorker.worker.js @@ -2,6 +2,7 @@ /* eslint-disable no-restricted-globals */ import DBC from "../models/can/dbc"; import DbcUtils from "../utils/dbc"; +import extend from "xtend"; function processStreamedCanMessages( newCanMessages, @@ -30,7 +31,8 @@ function processStreamedCanMessages( let busTimeSum = 0; for (let i = 0; i < canMessages.length; i++) { - let [address, busTime, data, source] = canMessages[i]; + let { address, busTime, data, bus } = canMessages[i]; + let prevBusTime; if (i === 0) { if (lastBusTime === null) { @@ -39,7 +41,7 @@ function processStreamedCanMessages( prevBusTime = lastBusTime; } } else { - prevBusTime = canMessages[i - 1][1]; + prevBusTime = canMessages[i - 1].busTime; } if (busTime >= prevBusTime) { @@ -47,11 +49,11 @@ function processStreamedCanMessages( } else { busTimeSum += 0x10000 - prevBusTime + busTime; } - const message = [...canMessages[i]]; - message[1] = time + busTimeSum / 500000.0; + const message = extend(canMessages[i]); + message.busTime = time + busTimeSum / 500000.0; if (firstCanTime === 0) { - firstCanTime = message[1]; + firstCanTime = message.busTime; } const msgEntry = DbcUtils.addCanMessage( @@ -67,7 +69,7 @@ function processStreamedCanMessages( } } - lastBusTime = canMessages[canMessages.length - 1][1]; + lastBusTime = canMessages[canMessages.length - 1].busTime; const newMaxByteStateChangeCount = DbcUtils.findMaxByteStateChangeCount( messages ); diff --git a/yarn.lock b/yarn.lock index 963f102..c362052 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,6 +17,18 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" +"@commaai/pandajs@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@commaai/pandajs/-/pandajs-0.1.0.tgz#55e7fcb880f22f2f2d814dd2437cddb88f4d69ce" + dependencies: + ap "^0.2.0" + can-message "^0.1.0" + is-browser "^2.0.1" + performance-now "^2.1.0" + raf "^3.4.0" + thyming "^0.1.1" + weakmap-event "^2.0.7" + "@types/node@*": version "8.5.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.1.tgz#4ec3020bcdfe2abffeef9ba3fbf26fca097514b5" @@ -157,6 +169,10 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" +ap@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110" + aphrodite@^1.2.1: version "1.2.5" resolved "https://registry.yarnpkg.com/aphrodite/-/aphrodite-1.2.5.tgz#8358c36c80bb03aee9b97165aaa70186225b4983" @@ -208,6 +224,10 @@ arr-flatten@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" +array-differ@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" @@ -281,6 +301,10 @@ asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" +assert-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-function/-/assert-function-1.0.0.tgz#aeb2ad0fe00888b41254a8e3934a562968d77905" + assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" @@ -1462,6 +1486,10 @@ camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" +can-message@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/can-message/-/can-message-0.1.0.tgz#288862d67a0dbd85dbc56da0a44f6e2bf1dfb030" + caniuse-api@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" @@ -3395,6 +3423,10 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +geval@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/geval/-/geval-2.2.0.tgz#5622b10a28028284198afe351f6b417042c33dda" + github-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/github-api/-/github-api-3.0.0.tgz#2832f98d0d3a83f1485e2db32c9259e4b9c40a75" @@ -3978,6 +4010,10 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" +is-browser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-browser/-/is-browser-2.0.1.tgz#8bf0baf799a9c62fd9de5bcee4cf3397c3e7529a" + is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -4648,6 +4684,13 @@ jsx-ast-utils@^2.0.0: dependencies: array-includes "^3.0.3" +key-difference@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/key-difference/-/key-difference-1.0.0.tgz#7ede6f69bfff08010b5612885303b688a30008cc" + dependencies: + array-differ "~1.0.0" + xtend "~4.0.0" + killable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b" @@ -7437,6 +7480,12 @@ thunky@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" +thyming@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/thyming/-/thyming-0.1.1.tgz#1c30f4fe168e6aae59b4c4cc23385accf4199205" + dependencies: + assert-function "^1.0.0" + time-stamp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" @@ -8038,6 +8087,19 @@ wbuf@^1.1.0, wbuf@^1.7.2: dependencies: minimalistic-assert "^1.0.0" +weakmap-event@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/weakmap-event/-/weakmap-event-2.0.7.tgz#3f65318aba89e022981e8249bfae339a4ad6945a" + dependencies: + geval "~2.2.0" + key-difference "~1.0.0" + weakmap-shim "~1.1.0" + xtend "~4.0.0" + +weakmap-shim@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/weakmap-shim/-/weakmap-shim-1.1.1.tgz#d65afd784109b2166e00ff571c33150ec2a40b49" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -8287,7 +8349,7 @@ xmlhttprequest@1: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" -xtend@^4.0.0, xtend@^4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"