can explorer progress; chffr free space check + build fix

main
Andy Haden 2017-05-29 16:52:17 -07:00
commit a40e982666
29 changed files with 3695 additions and 0 deletions

18
.gitignore vendored 100644
View File

@ -0,0 +1,18 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*

1623
README.md 100644

File diff suppressed because it is too large Load Diff

31
package.json 100644
View File

@ -0,0 +1,31 @@
{
"name": "can-explorer",
"version": "0.1.0",
"private": true,
"dependencies": {
"aphrodite": "^1.2.1",
"create-react-class": "^15.5.3",
"int64-buffer": "^0.1.9",
"prop-types": "^15.5.10",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-infinite": "^0.11.0",
"react-list": "^0.8.6",
"react-measure": "^1.4.7",
"react-vega": "^3.0.0"
},
"devDependencies": {
"react-scripts": "0.9.5"
},
"scripts": {
"start": "python simple-cors-http-server.py & react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"esLintConfig": {
"rules": {
"no-use-before-define": "off"
}
}
}

BIN
public/favicon.ico 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

31
public/index.html 100644
View File

@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tag above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start`.
To create a production bundle, use `npm run build`.
-->
</body>
</html>

View File

@ -0,0 +1,12 @@
#!/usr/bin/env python2
from SimpleHTTPServer import SimpleHTTPRequestHandler
import BaseHTTPServer
class CORSRequestHandler (SimpleHTTPRequestHandler):
def end_headers (self):
self.send_header('Access-Control-Allow-Origin', '*')
SimpleHTTPRequestHandler.end_headers(self)
if __name__ == '__main__':
BaseHTTPServer.test(CORSRequestHandler, BaseHTTPServer.HTTPServer)

124
src/CanExplorer.js 100644
View File

@ -0,0 +1,124 @@
import React, { Component } from 'react';
import { css, StyleSheet } from 'aphrodite/no-important';
import Meta from './components/meta';
import Explorer from './components/explorer';
import NumpyLoader from './utils/loadnpy';
import CivicDbc from './civic-dbc'
import {uint64BEToHex} from './models/can-msg-fmt';
const Int64LE = require('int64-buffer').Int64LE
export default class CanExplorer extends Component {
static propTypes = {
url: React.PropTypes.string,
max: React.PropTypes.number,
};
constructor(props) {
super(props);
this.state = {
messages: {},
selectedMessage: null,
};
this.loadCanPart = this.loadCanPart.bind(this);
}
parseMessage(time, address, data) {
return {time: time,
hexData: Buffer.from(data).toString('hex'),
signals: CivicDbc.getSignalValues(address, data)}
}
createMessageSpec(address, id, bus) {
return {name: CivicDbc.getMessageName(address),
address: address,
id: id,
bus: bus,
signalSpecs: CivicDbc.getSignalSpecs(address),
entries: []}
}
loadCanPart(base, num, messages) {
var urls = [ base+"/Log/"+num+"/can/t",
base+"/Log/"+num+"/can/src",
base+"/Log/"+num+"/can/address",
base+"/Log/"+num+"/can/data"];
return new Promise((resolve) => {
Promise.all(urls.map(NumpyLoader.promise)).then((da) => {
for (var i = 0; i < da[0].data.length; i++) {
var t = da[0].data[i];
var src = Int64LE(da[1].data, i*8).toString(10);
var address = Int64LE(da[2].data, i*8);
var addressHexStr = address.toString(16);
var id = src + ":" + addressHexStr;
var addressNum = address.toNumber();
var data = da[3].data.slice(i*8, (i+1)*8);
if (messages[id] === undefined) messages[id] = this.createMessageSpec(address.toNumber(), id, src);
const msg = this.parseMessage(t, address.toNumber(), data);
messages[id].entries.push(msg);
}
resolve(messages)
})
})
}
fetchLocalMessages() {
fetch('http://127.0.0.1:8000/src/dev-data/civic-messages.json').then((resp) => {
resp.json().then((messages) => {
Object.keys(messages).forEach((key) => {
const msg = messages[key];
msg['id'] = msg.bus + ':' + msg.address.toString(16)
messages[key] = msg;
});
this.setState({messages});
})
})
}
componentWillMount() {
this.fetchLocalMessages();
// This is currently way too slow. working with pre-processed CAN (see above)
// to build out frontend before handling perf issues.
// const messages = {};
// let promise = new Promise((resolve, reject) => {resolve({})});
// for(var i = 2; i <= 3; i++) {
// console.log(i);
// promise = promise.then((partialMessages) => {
// for(var id in partialMessages) {
// if(messages[id] === undefined) messages[id] = partialMessages[id];
// messages[id].entries = messages[id].entries.concat(partialMessages[id].entries);
// }
// this.setState({messages})
// console.log(messages)
// return this.loadCanPart(this.props.url, i, messages)
// });
// }
}
render() {
return (<div className={css(Styles.root)}>
<Meta url={this.props.url}
messages={this.state.messages}
onMessageSelected={(msg) => {this.setState({selectedMessage: msg})}} />
<Explorer
url={this.props.url}
messages={this.state.messages}
selectedMessage={this.state.selectedMessage} />
</div>)
}
}
const Styles = StyleSheet.create({
root: {
flexDirection: 'row',
display: 'flex',
}
});

View File

@ -0,0 +1,50 @@
import DBC, {swapOrder} from '../../models/can/dbc';
import Signal from '../../models/can/signal';
const Uint64BE = require('int64-buffer').Uint64BE
const DBC_MESSAGE_DEF = `BO_ 228 STEERING_CONTROL: 5 ADAS
SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
SG_ SET_ME_X00 : 22|7@0+ (1,0) [0|127] "" EPS
SG_ SET_ME_X00_2 : 31|8@0+ (1,0) [0|0] "" EPS
SG_ CHECKSUM : 39|4@0+ (1,0) [0|15] "" EPS
SG_ COUNTER : 33|2@0+ (1,0) [0|3] "" EPS`;
const steerTorqueSignal = new Signal({
name: 'STEER_TORQUE',
startBit: 7,
size: 16,
isLittleEndian: false,
isSigned: true,
factor: 1,
offset: 0,
min: -3840,
max: 3840,
unit: ""});
test('DBC parses steering control message', () => {
const dbcParsed = new DBC(DBC_MESSAGE_DEF);
const {signals} = dbcParsed.messages.get(228);
expect(Object.keys(signals).length).toBe(6);
expect(signals['STEER_TORQUE'].equals(steerTorqueSignal)).toBe(true);
});
test('swapOrder properly converts little endian to big endian', () => {
const littleEndianHex = 'e2d62a0bd0d3b5e5';
const bigEndianHex = 'e5b5d3d00b2ad6e2';
const littleEndianHexSwapped = swapOrder(littleEndianHex, 16, 2);
expect(littleEndianHexSwapped == bigEndianHex).toBe(true);
});
test('DBC parses steer torque field from hex', () => {
const dbc = new DBC(DBC_MESSAGE_DEF);
const hex = 'e2d62a0bd0d3b5e5';
const value = dbc.valueForSignal(steerTorqueSignal, hex);
expect(value).toBe(-7466);
});

View File

@ -0,0 +1,37 @@
import Signal from '../../models/can/signal';
const someSignalParams = {
name: 'STEER_TORQUE',
startBit: 7,
size: 16,
isLittleEndian: false,
isSigned: true,
factor: 1,
offset: 0,
min: -3840,
max: 3840,
unit: ""};
const someOtherSignalParams = {
name: 'DIFFERENT_NAME',
startBit: 0,
size: 16,
isLittleEndian: false,
isSigned: true,
factor: 1,
offset: 0,
min: -3840,
max: 3840,
unit: ""};
test('Signal.equals returns true for signals with identical properties', () => {
const someSignal = new Signal(someSignalParams);
const someEquivalentSignal = new Signal(someSignalParams);
expect(someSignal.equals(someEquivalentSignal)).toBe(true);
});
test('Signal.equals returns false for signals with different properties', () => {
const someSignal = new Signal(someSignalParams);
const differentSignal = new Signal(someOtherSignalParams);
expect(someSignal.equals(differentSignal)).toBe(false);
});

0
src/api/can.js 100644
View File

348
src/civic-dbc.js 100644
View File

@ -0,0 +1,348 @@
import DBC from './models/can/dbc';
const CivicDbc = new DBC(`
VERSION ""
NS_ :
NS_DESC_
CM_
BA_DEF_
BA_
VAL_
CAT_DEF_
CAT_
FILTER
BA_DEF_DEF_
EV_DATA_
ENVVAR_DATA_
SGTYPE_
SGTYPE_VAL_
BA_DEF_SGTYPE_
BA_SGTYPE_
SIG_TYPE_REF_
VAL_TABLE_
SIG_GROUP_
SIG_VALTYPE_
SIGTYPE_VALTYPE_
BO_TX_BU_
BA_DEF_REL_
BA_REL_
BA_DEF_DEF_REL_
BU_SG_REL_
BU_EV_REL_
BU_BO_REL_
SG_MUL_VAL_
BS_:
BU_: INTERCEPTOR EBCM NEO ADAS PCM EPS VSA SCM BDY XXX EPB
BO_ 57 XXX_1: 3 XXX
BO_ 148 XXX_2: 8 XXX
SG_ LAT_ACCEL : 7|10@0+ (0.02,-512) [-20|20] "m/s2" NEO
SG_ LONG_ACCEL : 24|9@0- (-0.02,0) [-20|20] "m/s2" NEO
BO_ 228 STEERING_CONTROL: 5 ADAS
SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
SG_ SET_ME_X00 : 22|7@0+ (1,0) [0|127] "" EPS
SG_ SET_ME_X00_2 : 31|8@0+ (1,0) [0|0] "" EPS
SG_ CHECKSUM : 39|4@0+ (1,0) [0|15] "" EPS
SG_ COUNTER : 33|2@0+ (1,0) [0|3] "" EPS
BO_ 304 GAS_PEDAL2: 8 PCM
SG_ ENGINE_TORQUE_ESTIMATE : 7|16@0- (1,0) [-1000|1000] "Nm" NEO
SG_ ENGINE_TORQUE_REQUEST : 23|16@0- (1,0) [-1000|1000] "Nm" NEO
SG_ CAR_GAS : 39|8@0+ (1,0) [0|255] "" NEO
BO_ 330 STEERING_SENSORS: 8 EPS
SG_ STEER_ANGLE : 7|16@0- (-0.1,0) [-500|500] "deg" NEO
SG_ STEER_ANGLE_RATE : 23|16@0- (-1,0) [-3000|3000] "deg/s" NEO
SG_ STEER_ANGLE_OFFSET : 39|8@0- (-0.1,0) [-128|127] "deg" NEO
SG_ STEER_WHEEL_ANGLE : 47|16@0- (-0.1,0) [-500|500] "deg" NEO
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" NEO
BO_ 344 POWERTRAIN_DATA: 8 PCM
SG_ XMISSION_SPEED : 7|16@0+ (0.002759506,0) [0|70] "m/s" NEO
SG_ ENGINE_RPM : 23|16@0+ (1,0) [0|15000] "rpm" NEO
SG_ XMISSION_SPEED2 : 39|16@0+ (0.002759506,0) [0|70] "m/s" NEO
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" NEO
BO_ 380 POWERTRAIN_DATA2: 8 PCM
SG_ PEDAL_GAS : 7|8@0+ (1,0) [0|255] "" NEO
SG_ ENGINE_RPM : 23|16@0+ (1,0) [0|15000] "rpm" NEO
SG_ GAS_PRESSED : 39|1@0+ (1,0) [0|1] "" NEO
SG_ ACC_STATUS : 38|1@0+ (1,0) [0|1] "rpm" NEO
SG_ BOH_17C : 37|5@0+ (1,0) [0|1] "rpm" NEO
SG_ BRAKE_LIGHTS_ON : 32|1@0+ (1,0) [0|1] "rpm" NEO
SG_ BOH2_17C : 47|10@0+ (1,0) [0|1] "rpm" NEO
SG_ BRAKE_PRESSED : 53|1@0+ (1,0) [0|1] "" NEO
SG_ BOH3_17C : 52|5@0+ (1,0) [0|1] "rpm" NEO
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" NEO
BO_ 399 STEER_STATUS: 7 EPS
SG_ STEER_TORQUE_SENSOR : 7|16@0- (1,0) [-31000|31000] "tbd" NEO
SG_ STEER_TORQUE_MOTOR : 23|16@0- (1,0) [-31000|31000] "tbd" NEO
SG_ STEER_STATUS : 39|4@0+ (1,0) [0|15] "" NEO
SG_ STEER_CONTROL_ACTIVE : 35|1@0+ (1,0) [0|1] "" NEO
SG_ COUNTER : 53|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 51|4@0+ (1,0) [0|3] "" NEO
BO_ 401 GEARBOX: 8 PCM
SG_ GEAR_SHIFTER : 5|6@0+ (1,0) [0|63] "" NEO
SG_ GEAR : 35|4@0+ (1,0) [0|15] "" NEO
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" NEO
BO_ 420 VSA_STATUS: 8 VSA
SG_ USER_BRAKE : 7|16@0+ (0.015625,-1.609375) [0|1000] "" NEO
SG_ ESP_DISABLED : 28|1@0+ (1,0) [0|1] "" NEO
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" NEO
BO_ 427 XXX_3: 3 VSA
BO_ 428 XXX_4: 8 XXX
BO_ 432 STANDSTILL: 7 VSA
SG_ WHEELS_MOVING : 12|1@0+ (1,0) [0|1] "" NEO
SG_ BRAKE_ERROR_1 : 11|1@0+ (1,0) [0|1] "" NEO
SG_ BRAKE_ERROR_2 : 9|1@0+ (1,0) [0|1] "" NEO
SG_ COUNTER : 53|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 51|4@0+ (1,0) [0|3] "" NEO
BO_ 450 XXX_5: 8 EPB
SG_ EPB_ACTIVE : 3|1@0+ (1,0) [0|1] "" NEO
SG_ EPB_STATE : 29|2@0+ (1,0) [0|3] "" NEO
BO_ 464 WHEEL_SPEEDS: 8 VSA
SG_ WHEEL_SPEED_FL : 7|15@0+ (0.002759506,0) [0|70] "m/s" NEO
SG_ WHEEL_SPEED_FR : 8|15@0+ (0.002759506,0) [0|70] "m/s" NEO
SG_ WHEEL_SPEED_RL : 25|15@0+ (0.002759506,0) [0|70] "m/s" NEO
SG_ WHEEL_SPEED_RR : 42|15@0+ (0.002759506,0) [0|70] "m/s" NEO
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" NEO
BO_ 470 XXX_6: 2 VSA
BO_ 476 XXX_7: 7 XXX
BO_ 487 XXX_8: 4 VSA
SG_ BRAKE_PRESSURE1 : 7|10@0+ (0.015625,-103) [0|1000] "" NEO
SG_ BRAKE_PRESSURE2 : 9|10@0+ (0.015625,-103) [0|1000] "" NEO
BO_ 490 VEHICLE_DYNAMICS: 8 VSA
SG_ LONG_ACCEL : 23|16@0- (0.0015384,0) [-20|20] "m/s2" NEO
BO_ 493 XXX_9: 5 VSA
BO_ 506 BRAKE_COMMAND: 8 ADAS
SG_ COMPUTER_BRAKE : 7|10@0+ (0.003906248,0) [0|1] "" EBCM
SG_ ZEROS_BOH : 13|5@0+ (1,0) [0|1] "" EBCM
SG_ COMPUTER_BRAKE_REQUEST : 8|1@0+ (1,0) [0|1] "" EBCM
SG_ CRUISE_BOH2 : 23|3@0+ (1,0) [0|1] "" EBCM
SG_ CRUISE_OVERRIDE : 20|1@0+ (1,0) [0|1] "" EBCM
SG_ CRUISE_BOH3 : 19|1@0+ (1,0) [0|1] "" EBCM
SG_ CRUISE_FAULT_CMD : 18|1@0+ (1,0) [0|1] "" EBCM
SG_ CRUISE_CANCEL_CMD : 17|1@0+ (1,0) [0|1] "" EBCM
SG_ COMPUTER_BRAKE_REQUEST_2 : 16|1@0+ (1,0) [0|1] "" EBCM
SG_ SET_ME_0X80 : 31|8@0+ (1,0) [0|1] "" EBCM
SG_ BRAKE_LIGHTS : 39|1@0+ (1,0) [0|1] "" EBCM
SG_ CRUISE_STATES : 38|7@0+ (1,0) [0|1] "" EBCM
SG_ CHIME : 47|3@0+ (1,0) [0|7] "" EBCM
SG_ ZEROS_BOH6 : 44|1@0+ (1,0) [0|1] "" EBCM
SG_ FCW : 43|1@0+ (1,0) [0|3] "" EBCM
SG_ ZEROS_BOH3 : 42|2@0+ (1,0) [0|0] "" EBCM
SG_ FCW2 : 40|1@0+ (1,0) [0|0] "" EBCM
SG_ ZEROS_BOH4 : 55|8@0+ (1,0) [0|0] "" EBCM
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EBCM
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EBCM
BO_ 512 GAS_COMMAND: 3 NEO
SG_ GAS_COMMAND : 7|16@0+ (0.253984064,-328) [0|1] "" INTERCEPTOR
SG_ COUNTER : 21|2@0+ (1,0) [0|3] "" INTERCEPTOR
SG_ CHECKSUM : 19|4@0+ (1,0) [0|3] "" INTERCEPTOR
BO_ 513 GAS_SENSOR: 5 INTERCEPTOR
SG_ INTERCEPTOR_GAS : 7|16@0+ (0.253984064,-328) [0|1] "" NEO
SG_ INTERCEPTOR_GAS2 : 23|16@0+ (0.126992032,-656) [0|1] "" NEO
SG_ COUNTER : 37|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 35|4@0+ (1,0) [0|3] "" NEO
BO_ 545 XXX_10: 6 XXX
BO_ 597 ROUGH_WHEEL_SPEED: 8 VSA
SG_ WHEEL_SPEED_FL : 7|8@0+ (1,0) [0|255] "mph" NEO
SG_ WHEEL_SPEED_FR : 15|8@0+ (1,0) [0|255] "mph" NEO
SG_ WHEEL_SPEED_RL : 23|8@0+ (1,0) [0|255] "mph" NEO
SG_ WHEEL_SPEED_RR : 31|8@0+ (1,0) [0|255] "mph" NEO
SG_ SET_TO_X55 : 39|8@0+ (1,0) [0|255] "" NEO
SG_ SET_TO_X55 : 47|8@0+ (1,0) [0|255] "" NEO
BO_ 662 CRUISE_BUTTONS: 4 SCM
SG_ CRUISE_BUTTONS : 7|3@0+ (1,0) [0|7] "" NEO
SG_ CRUISE_SETTING : 3|2@0+ (1,0) [0|3] "" NEO
BO_ 773 SEATBELT_STATUS: 7 BDY
SG_ SEATBELT_DRIVER_LAMP : 7|1@0+ (1,0) [0|1] "" NEO
SG_ SEATBELT_DRIVER_LATCHED : 13|1@0+ (1,0) [0|1] "" NEO
SG_ COUNTER : 53|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 51|4@0+ (1,0) [0|3] "" NEO
BO_ 777 XXX_11: 8 XXX
BO_ 780 ACC_HUD: 8 ADAS
SG_ PCM_SPEED : 7|16@0+ (0.002759506,0) [0|100] "m/s" BDY
SG_ PCM_GAS : 23|7@0+ (1,0) [0|127] "" BDY
SG_ ZEROS_BOH : 16|1@0+ (1,0) [0|255] "" BDY
SG_ CRUISE_SPEED : 31|8@0+ (1,0) [0|255] "" BDY
SG_ DTC_MODE : 39|1@0+ (1,0) [0|1] "" BDY
SG_ BOH : 38|1@0+ (1,0) [0|1] "" BDY
SG_ ACC_PROBLEM : 37|1@0+ (1,0) [0|1] "" BDY
SG_ FCM_OFF : 36|1@0+ (1,0) [0|1] "" BDY
SG_ BOH_2 : 35|1@0+ (1,0) [0|1] "" BDY
SG_ FCM_PROBLEM : 34|1@0+ (1,0) [0|1] "" BDY
SG_ RADAR_OBSTRUCTED : 33|1@0+ (1,0) [0|1] "" BDY
SG_ ENABLE_MINI_CAR : 32|1@0+ (1,0) [0|1] "" BDY
SG_ HUD_DISTANCE : 47|2@0+ (1,0) [0|3] "" BDY
SG_ HUD_LEAD : 45|2@0+ (1,0) [0|3] "" BDY
SG_ BOH_3 : 43|1@0+ (1,0) [0|3] "" BDY
SG_ BOH_4 : 42|1@0+ (1,0) [0|3] "" BDY
SG_ BOH_5 : 41|1@0+ (1,0) [0|3] "" BDY
SG_ CRUISE_CONTROL_LABEL : 40|1@0+ (1,0) [0|3] "" BDY
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" BDY
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" BDY
BO_ 795 XXX_12: 8 XXX
BO_ 800 XXX_13: 8 XXX
BO_ 804 CRUISE: 8 PCM
SG_ ENGINE_TEMPERATURE : 7|8@0+ (1,0) [0|255] "" NEO
SG_ BOH : 15|8@0+ (1,0) [0|255] "" NEO
SG_ TRIP_FUEL_CONSUMED : 23|16@0+ (1,0) [0|255] "" NEO
SG_ CRUISE_SPEED_PCM : 39|8@0+ (1,0) [0|255] "" NEO
SG_ BOH2 : 47|8@0- (1,0) [0|255] "" NEO
SG_ BOH3 : 55|8@0+ (1,0) [0|255] "" NEO
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" NEO
BO_ 806 SCM_FEEDBACK: 8 SCM
SG_ CMBS_BUTTON : 22|2@0+ (1,0) [0|3] "" NEO
SG_ MAIN_ON : 28|1@0+ (1,0) [0|1] "" NEO
SG_ RIGHT_BLINKER : 27|1@0+ (1,0) [0|1] "" NEO
SG_ LEFT_BLINKER : 26|1@0+ (1,0) [0|1] "" NEO
BO_ 808 XXX_14: 8 XXX
BO_ 829 LKAS_HUD_2: 5 ADAS
SG_ CAM_TEMP_HIGH : 7|1@0+ (1,0) [0|255] "" BDY
SG_ BOH : 6|7@0+ (1,0) [0|127] "" BDY
SG_ DASHED_LANES : 14|1@0+ (1,0) [0|1] "" BDY
SG_ DTC : 13|1@0+ (1,0) [0|1] "" BDY
SG_ LKAS_PROBLEM : 12|1@0+ (1,0) [0|1] "" BDY
SG_ LKAS_OFF : 11|1@0+ (1,0) [0|1] "" BDY
SG_ SOLID_LANES : 10|1@0+ (1,0) [0|1] "" BDY
SG_ LDW_RIGHT : 9|1@0+ (1,0) [0|1] "" BDY
SG_ STEERING_REQUIRED : 8|1@0+ (1,0) [0|1] "" BDY
SG_ BOH : 23|2@0+ (1,0) [0|4] "" BDY
SG_ LDW_PROBLEM : 21|1@0+ (1,0) [0|1] "" BDY
SG_ BEEP : 17|2@0+ (1,0) [0|1] "" BDY
SG_ LDW_ON : 28|1@0+ (1,0) [0|1] "" BDY
SG_ LDW_OFF : 27|1@0+ (1,0) [0|1] "" BDY
SG_ CLEAN_WINDSHIELD : 26|1@0+ (1,0) [0|1] "" BDY
SG_ SET_ME_X48 : 31|8@0+ (1,0) [0|255] "" BDY
SG_ COUNTER : 37|2@0+ (1,0) [0|3] "" BDY
SG_ CHECKSUM : 35|4@0+ (1,0) [0|3] "" BDY
BO_ 862 XXX_15: 8 ADAS
SG_ UI_ALERTS : 7|56@0+ (1,0) [0|127] "" BDY
BO_ 884 XXX_16: 8 XXX
BO_ 891 XXX_17: 8 XXX
BO_ 892 XXX_18: 8 XXX
BO_ 927 XXX_19: 8 ADAS
SG_ ZEROS_BOH : 7|17@0+ (1,0) [0|127] "" BDY
SG_ APPLY_BRAKES_FOR_CANC : 23|1@0+ (1,0) [0|15] "" BDY
SG_ ZEROS_BOH2 : 22|1@0+ (1,0) [0|1] "" BDY
SG_ RESUME_INSTRUCTION : 21|1@0+ (1,0) [0|15] "" BDY
SG_ ACC_ALERTS : 20|5@0+ (1,0) [0|15] "" BDY
SG_ ZEROS_BOH2 : 31|8@0+ (1,0) [0|127] "" BDY
SG_ LEAD_SPEED : 39|9@0+ (1,0) [0|127] "" BDY
SG_ LEAD_STATE : 46|3@0+ (1,0) [0|127] "" BDY
SG_ LEAD_DISTANCE : 43|5@0+ (1,0) [0|31] "" BDY
SG_ ZEROS_BOH3 : 54|7@0+ (1,0) [0|127] "" BDY
BO_ 929 XXX_20: 8 XXX
BO_ 985 XXX_21: 3 XXX
BO_ 1024 XXX_22: 5 XXX
BO_ 1027 XXX_23: 5 XXX
BO_ 1029 DOORS_STATUS: 8 BDY
SG_ DOOR_OPEN_FL : 37|1@0+ (1,0) [0|1] "" NEO
SG_ DOOR_OPEN_FR : 38|1@0+ (1,0) [0|1] "" NEO
SG_ DOOR_OPEN_RL : 39|1@0+ (1,0) [0|1] "" NEO
SG_ DOOR_OPEN_RR : 40|1@0+ (1,0) [0|1] "" NEO
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" NEO
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" NEO
BO_ 1036 XXX_24: 8 XXX
BO_ 1039 XXX_25: 8 XXX
BO_ 1108 XXX_26: 8 XXX
BO_ 1302 XXX_27: 8 XXX
BO_ 1322 XXX_28: 5 XXX
BO_ 1361 XXX_29: 5 XXX
BO_ 1365 XXX_30: 5 XXX
BO_ 1424 XXX_31: 5 XXX
BO_ 1600 XXX_32: 5 XXX
BO_ 1601 XXX_33: 8 XXX
BO_ 1633 XXX_34: 8 XXX
BO_TX_BU_ 228 : NEO,ADAS;
BO_TX_BU_ 506 : NEO,ADAS;
BO_TX_BU_ 780 : NEO,ADAS;
BO_TX_BU_ 829 : NEO,ADAS;
BO_TX_BU_ 862 : NEO,ADAS;
BO_TX_BU_ 927 : NEO,ADAS;
CM_ SG_ 401 GEAR "10 = reverse, 11 = transition";
CM_ SG_ 490 LONG_ACCEL "wheel speed derivative, noisy and zero snapping";
CM_ SG_ 780 CRUISE_SPEED "255 = no speed";
CM_ SG_ 804 CRUISE_SPEED_PCM "255 = no speed";
CM_ SG_ 829 BEEP "beeps are pleasant, chimes are for warnngs etc...";
VAL_ 399 STEER_STATUS 5 "fault" 4 "no_torque_alert_2" 2 "no_torque_alert_1" 0 "normal" ;
VAL_ 401 GEAR_SHIFTER 32 "L" 16 "S" 8 "D" 4 "N" 2 "R" 1 "P" ;
VAL_ 450 EPB_STATE 3 "engaged" 2 "disengaging" 1 "engaging" 0 "disengaged" ;
VAL_ 506 CHIME 4 "double_chime" 3 "single_chime" 2 "continuous_chime" 1 "repeating_chime" 0 "no_chime" ;
VAL_ 662 CRUISE_BUTTONS 7 "tbd" 6 "tbd" 5 "tbd" 4 "accel_res" 3 "decel_set" 2 "cancel" 1 "main" 0 "none" ;
VAL_ 662 CRUISE_SETTING 3 "distance_adj" 2 "tbd" 1 "lkas_button" 0 "none" ;
VAL_ 780 CRUISE_SPEED 255 "no_speed" 252 "stopped" ;
VAL_ 780 HUD_LEAD 3 "acc_off" 2 "solid_car" 1 "dashed_car" 0 "no_car" ;
VAL_ 806 CMBS_BUTTON 3 "pressed" 0 "released" ;
VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ;
VAL_ 927 ACC_ALERTS 29 "esp_active_acc_canceled" 10 "b_pedal_applied" 9 "speed_too_low" 8 "speed_too_high" 7 "p_brake_applied" 6 "gear_no_d" 5 "seatbelt" 4 "too_steep_downhill" 3 "too_steep_uphill" 2 "too_close" 1 "no_vehicle_ahead" ;
`);
export default CivicDbc;

View File

@ -0,0 +1,135 @@
import React, {Component} from 'react';
import { StyleSheet, css } from 'aphrodite/no-important';
import Vega from 'react-vega';
import PropTypes from 'prop-types';
import Signal from '../models/can/signal';
import CanPlot from '../vega/CanPlot';
const spec = {
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
"width": 500,
"height": 200,
"padding": 5,
"data": [
{
"name": "table"
}
],
"scales": [
{
"name": "xscale",
"type": "linear",
"range": "width",
"zero": false,
"domain": {"data": "table", "field": "x"}
},
{
"name": "yscale",
"type": "linear",
"range": "height",
"zero": true,
"domain": {"data": "table", "field": "y"}
}
],
"axes": [
{"orient": "bottom", "scale": "xscale"},
{"orient": "left", "scale": "yscale"}
],
"marks": [
{
"type": "line",
"from": {"data": "table"},
"encode": {
"enter": {
"x": {"scale": "xscale", "field": "x"},
"y": {"scale": "yscale", "field": "y"},
},
"hover": {
"fillOpacity": {"value": 0.5}
}
}
}
]
};
export default class CanGraph extends Component {
static propTypes = {
data: PropTypes.array,
messageName: PropTypes.string,
signalSpec: PropTypes.instanceOf(Signal),
segment: PropTypes.array
};
constructor(props) {
super(props);
this.onNewView = this.onNewView.bind(this);
}
segmentIsNew(newSegment) {
return newSegment.length != this.props.segment.length
|| !(newSegment.every((val, idx) => this.props.segment[idx] == val));
}
shouldComponentUpdate(nextProps, nextState) {
if(this.view) {
// only update if segment is new
if(this.segmentIsNew(nextProps.segment)) {
if(nextProps.segment.length > 0) {
// Set segmented domain
this.view.signal('segment', nextProps.segment)
} else {
// Reset segment to full domain
const xVals = this.props.data.map((d) => d.x);
const min = Math.min.apply(null, xVals);
const max = Math.max.apply(null, xVals);
this.view.signal('segment', [min, max]);
}
this.view.run();
}
}
return nextProps.data.length != this.props.data.length;
}
onNewView(view) {
this.view = view;
if(this.props.segment.length > 0) {
view.signal('segment', this.props.segment);
}
}
render() {
return (<div className={css(Styles.root)}>
<p className={css(Styles.messageName)}>{this.props.messageName}</p>
<p className={css(Styles.signalName)}>{this.props.signalSpec.name}</p>
<CanPlot logLevel={0}
data={{table: this.props.data}}
onNewView={this.onNewView}
/>
</div>);
}
}
const Styles = StyleSheet.create({
root: {
borderBottomWidth: '1px',
borderColor: 'gray',
},
messageName: {
fontSize: 12,
color: 'rgba(0,0,0,0.5)',
margin: 0
},
signalName: {
fontSize: 16,
margin: 0
}
});

View File

@ -0,0 +1,44 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, css } from 'aphrodite/no-important';
import CanHistogramPlot from '../vega/CanHistogramPlot';
export default class CanHistogram extends Component {
static propTypes = {
message: PropTypes.object,
onSegmentChanged: PropTypes.func
};
constructor(props) {
super(props);
}
render() {
return (<div className={css(Styles.root)}>
{this.props.message ?
(<div>
<CanHistogramPlot
logLevel={0}
data={{points: this.props.message.entries}}
onSignalSegment={(signal, segment) => {this.props.onSegmentChanged(segment)}}
/>
<p className={css(Styles.label)}>{this.props.message.name} per time</p>
</div>)
: null}
</div>);
}
}
const Styles = StyleSheet.create({
root: {
borderBottom: '1px solid rgba(0,0,0,0.9)',
borderColor: 'gray',
width: '1000px'
},
label: {
textAlign: 'center',
color: 'rgba(0,0,0,0.9)',
margin: 0
}
});

View File

@ -0,0 +1,236 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import ReactList from 'react-list';
import Measure from 'react-measure';
import { StyleSheet, css } from 'aphrodite/no-important';
import { formatMsgDec, formatMsgHex } from '../models/can-msg-fmt';
export default class CanLog extends Component {
static ITEMS_PER_PAGE = 50;
static propTypes = {
plottedSignals: PropTypes.array,
onSignalUnplotPressed: PropTypes.func,
onSignalPlotPressed: PropTypes.func
};
constructor(props) {
super(props);
// only want to display up to length elements at a time
// offset, length
this.state = {
offset: 0,
length: 0,
msgDisplayFormat: 'name',
expandedMessages: [],
messageHeights: []
}
this.onChoicePress = this.onChoicePress.bind(this);
this.messageRow = this.messageRow.bind(this);
this.addDisplayedMessages = this.addDisplayedMessages.bind(this);
this.renderMessage = this.renderMessage.bind(this);
this.renderTable = this.renderTable.bind(this);
}
componentWillReceiveProps(nextProps) {
if(nextProps.data) {
this.addDisplayedMessages();
}
}
addDisplayedMessages() {
const {length, messageHeights} = this.state;
const newLength = length + CanLog.ITEMS_PER_PAGE;
for(let i = length; i < newLength; i++) {
messageHeights.push(Styles.dataRow.height);
}
this.setState({length: newLength, messageHeights});
}
onChoicePress(fmt) {
this.setState({msgDisplayFormat: fmt})
}
expandMessage(msg, msgIdx) {
// const {messageHeights} = this.state;
// messageHeights[msgIdx] = TODO dynamic height calc if message expanded.
// Also could pre-compute height of each message Id instead of row (as signals are consistent), which would be cheaper.
this.setState({expandedMessages: this.state.expandedMessages.concat([msg.time])})
}
collapseMessage(msg, msgIdx) {
this.setState({expandedMessages: this.state.expandedMessages
.filter((expMsgTime) => expMsgTime !== msg.time)})
}
isSignalPlotted(msgId, signalName) {
const plottedSignal = this.props.plottedSignals.find((signal) => signal.messageId == msgId && signal.name == signalName);
return plottedSignal !== undefined;
}
expandedMessage(msg) {
return (<div className={css(Styles.row, Styles.signalRow)} key={msg.time + '-expanded'}>
<div className={css(Styles.col)}>
<div className={css(Styles.signalCol)}>
<table>
<tbody>
{Object.entries(msg.signals).map(([name, value]) => {
return [name, value, this.isSignalPlotted(this.props.data.id, name)]
}).sort(([name1, value1, isPlotted1], [name2, value2, isPlotted2]) => {
// Display plotted signals first
if(isPlotted1 && !isPlotted2) {
return -1;
} else if(isPlotted1 && isPlotted2) {
return 0;
} else {
return 1;
}
}).map(([name, value, isPlotted]) => {
const {unit} = this.props.data.signalSpecs[name];
return (<tr key={name}>
<td>{name}</td>
<td>{value} {unit}</td>
{isPlotted ?
<td className={css(Styles.plotSignal)}
onClick={() => {this.props.onSignalUnplotPressed(this.props.data.id, name)}}>[unplot]</td>
:
<td className={css(Styles.plotSignal)}
onClick={() => {this.props.onSignalPlotPressed(this.props.data.id, name)}}>[plot]</td>
}
</tr>);
})}
</tbody>
</table>
</div>
</div>
</div>)
}
isMessageExpanded(msg) {
return this.state.expandedMessages.indexOf(msg.time) !== -1;
}
messageRow(msg, key) {
const msgIsExpanded = this.isMessageExpanded(msg);
const row = [<div key={key}
className={css(Styles.dataRow, Styles.row)}
onClick={() => {
if(msgIsExpanded) {
this.collapseMessage(msg);
} else {
this.expandMessage(msg);
}
}}>
{msgIsExpanded ? <div className={css(Styles.col)}>&uarr;</div>
:
<div className={css(Styles.col)}>&darr;</div>}
<div className={css(Styles.col, Styles.timefieldCol)}>{msg.time}</div>
<div className={css(Styles.col, Styles.dataCol)}>{this.state.msgDisplayFormat == 'name' ? this.props.data.name : msg.hexData}</div>
</div>];
if(msgIsExpanded) {
row.push(this.expandedMessage(msg));
}
return row;
}
renderMessage(index, key) {
return this.messageRow(this.props.data.entries[this.state.offset + index], key);
}
renderTable(items, ref) {
return (<div className={css(Styles.root)}>
<div className={css(Styles.row)}>
<div className={css(Styles.col, Styles.dropdownCol)}>&nbsp;</div>
<div className={css(Styles.col, Styles.timefieldCol)}>Time</div>
<div className={css(Styles.dataCol)}>
Message
(<span className={css(Styles.messageFormatChoice,
(this.state.msgDisplayFormat == 'name' ? Styles.messageFormatChoiceSelected
: Styles.messageFormatChoiceUnselected))}
onClick={() => {this.onChoicePress('name')}}>Name</span>
<span> / </span>
<span className={css(Styles.messageFormatChoice,
(this.state.msgDisplayFormat == 'hex' ? Styles.messageFormatChoiceSelected
: Styles.messageFormatChoiceUnselected))}
onClick={() => {this.onChoicePress('hex')}}>Hex</span>)
</div>
</div>
<div className={css(Styles.tableRowGroup)}
ref={ref}>
{items}
</div>
</div>)
}
render() {
return <ReactList
itemRenderer={this.renderMessage}
itemsRenderer={this.renderTable}
length={this.props.data ? this.props.data.entries.length : 0}
pageSize={50}
type='variable' />;
}
}
const Styles = StyleSheet.create({
root: {
borderBottomWidth: '1px',
borderColor: 'gray',
width: '100%',
display: 'table',
tableLayout: 'fixed'
},
row: {
display: 'table-row',
width: 'auto',
clear: 'both',
},
dataRow: {
cursor: 'pointer',
},
tableRowGroup: {
display: 'table-row-group'
},
signalCol: {
width: '1px'
},
col: {
display: 'table-cell',
},
dropdownCol: {
width: '10px',
padding: 0,
margin: 0
},
timefieldCol: {
},
dataCol: {
},
messageFormatHeader: {
},
messageFormatChoice: {
cursor: 'pointer',
},
messageFormatChoiceSelected: {
fontWeight: 'bold'
},
messageFormatChoiceUnselected: {
color: 'gray'
},
plotSignal: {
cursor: 'pointer',
':hover': {
textDecoration: 'underline'
}
}
});

View File

@ -0,0 +1,17 @@
import React, {Component} from 'react';
import { StyleSheet, css } from 'aphrodite/no-important';
export default class NearestFrame extends Component {
render() {
return (<div className={css(Styles.root)}>
</div>);
}
}
const Styles = StyleSheet.create({
root: {
borderBottomWidth: '1px',
borderColor: 'gray',
}
});

View File

@ -0,0 +1,111 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, css } from 'aphrodite/no-important';
import CanHistogram from './CanHistogram';
import CanGraph from './CanGraph';
import NearestFrame from './NearestFrame';
import CanLog from './CanLog';
export default class Explorer extends Component {
static propTypes = {
selectedMessage: PropTypes.string,
url: PropTypes.string,
messages: PropTypes.objectOf(PropTypes.object)
};
constructor(props) {
super(props);
this.state = {
plottedSignals: [],
segment: []
}
this.onSignalPlotPressed = this.onSignalPlotPressed.bind(this);
this.onSignalUnplotPressed = this.onSignalUnplotPressed.bind(this);
this.onSegmentChanged = this.onSegmentChanged.bind(this);
}
graphData(msg, signalName) {
if(!msg) return null;
return msg.entries.map((entry) => {
return {x: entry.time,
y: entry.signals[signalName],
unit: msg.signalSpecs[signalName].unit}
});
}
onSignalPlotPressed(messageId, name) {
const {plottedSignals} = this.state;
this.setState({plottedSignals: plottedSignals.concat([{messageId, name}])})
}
onSignalUnplotPressed(messageId, name) {
const {plottedSignals} = this.state;
const newPlottedSignals = plottedSignals.filter((signal) => !(signal.messageId == messageId && signal.name == name));
this.setState({plottedSignals: newPlottedSignals})
}
onSegmentChanged(segment) {
if(Array.isArray(segment)) {
this.setState({segment})
}
}
render() {
return (<div className={css(Styles.root)}>
<CanHistogram
message={this.props.messages[this.props.selectedMessage]}
onSegmentChanged={this.onSegmentChanged}
/>
<div className={css(Styles.dataContainer)}>
<div className={css(Styles.left)}>
<CanLog data={this.props.messages[this.props.selectedMessage]}
plottedSignals={this.state.plottedSignals}
onSignalPlotPressed={this.onSignalPlotPressed}
onSignalUnplotPressed={this.onSignalUnplotPressed} />
</div>
<div className={css(Styles.right)}>
{this.state.plottedSignals.map(({messageId, name}) => {
const msg = this.props.messages[messageId];
return <CanGraph key={messageId + '_' + name}
messageName={msg.name}
signalSpec={msg.signalSpecs[name]}
segment={this.state.segment}
data={this.graphData(msg, name)} />
})}
<NearestFrame messages={this.props.messages}
selectedMessage={this.props.selectedMessage}
/>
</div>
</div>
</div>);
}
}
const Styles = StyleSheet.create({
root: {
flexDirection: 'column',
flex: 4,
width: '100%',
},
dataContainer: {
paddingTop: '10px',
paddingLeft: '10px',
flexDirection: 'row',
flex: 1,
display: 'flex',
width: '100%',
height: '100vh'
},
left: {
flex: '2 3 auto',
overflow: 'auto'
},
right: {
flex: '4 1',
}
})

View File

@ -0,0 +1,112 @@
import React, {Component} from 'react';
import { StyleSheet, css } from 'aphrodite/no-important';
import PropTypes from 'prop-types';
export default class Meta extends Component {
static propTypes = {
onMessageSelected: PropTypes.func,
url: PropTypes.string,
messages: PropTypes.objectOf(PropTypes.object)
};
constructor(props) {
super(props);
this.state = {
filterText: 'Filter'
};
this.onFilterChanged = this.onFilterChanged.bind(this);
this.onFilterFocus = this.onFilterFocus.bind(this);
this.msgKeyFilter = this.msgKeyFilter.bind(this);
}
onFilterChanged(e) {
this.setState({filterText: e.target.value})
}
onFilterFocus(e) {
if(this.state.filterText == 'Filter') {
this.setState({filterText: ''})
}
}
msgKeyFilter(key) {
const {filterText} = this.state;
const msgName = this.props.messages[key].name || '';
return (filterText == 'Filter'
|| filterText == ''
|| key.toLowerCase().indexOf(filterText.toLowerCase()) !== -1
|| msgName.toLowerCase().indexOf(filterText.toLowerCase()) !== -1);
}
render() {
return (
<div className={css(Styles.root)}>
<div>
<span className={css(Styles.titleText)}>
comma cabana
</span>
</div>
<div>
<img src="http://www.westingrandcayman.com/wp-content/uploads/2017/01/westin-grand-cayman-cabana-luxury.jpg" height="133" />
</div>
<div className={css(Styles.routeMeta)}>
<p>{this.props.url.match(/comma-([a-zA-Z0-9]+)/)[1]}</p>
<p>{this.props.url.match(/_(.+)/)[1]}</p>
</div>
<div>
<input type="text"
defaultValue="Filter"
value={this.state.filterText}
onFocus={this.onFilterFocus}
onChange={this.onFilterChanged} />
<ul className={css(Styles.messageList)}>
{Object.keys(this.props.messages)
.filter(this.msgKeyFilter)
.sort()
.map((key) => (
<li onClick={() => {this.props.onMessageSelected(key)}}
key={key}
className={css(Styles.message)}>{this.props.messages[key].name} ({key})</li>
))}
</ul>
</div>
</div>
);
}
}
const Styles = StyleSheet.create({
root: {
maxWidth: 225,
padding: 10,
flex: 1,
borderColor: 'gray',
borderRight: 'solid',
borderRightWidth: 2,
},
titleText: {
fontFamily: 'monospace',
paddingRight: 10,
fontSize: 24
},
routeMeta: {
borderBottomWidth: '1px',
borderColor: 'grey',
'*': {
display: 'inline-block'
}
},
message: {
cursor: 'pointer',
':hover' : {
backgroundColor: 'rgba(0,0,0,0.1)'
},
marginTop: 5
},
messageList: {
margin: 0,
padding: 0
},
});

File diff suppressed because one or more lines are too long

16
src/index.css 100644
View File

@ -0,0 +1,16 @@
html, body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
font-family: sans-serif;
}
#root {
width: 100%;
height: 100%;
}
* {
box-sizing: border-box;
}

11
src/index.js 100644
View File

@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import CanExplorer from './CanExplorer';
import './index.css';
ReactDOM.render(
<CanExplorer
url={"https://s3-us-west-2.amazonaws.com/chffrprivate2/v1/comma-2d7526b1faf1a2ca/586e2db5b03b3d653b1bec7e521459f1_2017-05-14--18-25-39"}
max={53} />,
document.getElementById('root')
);

7
src/logo.svg 100644
View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,36 @@
const Uint64BE = require('int64-buffer').Uint64BE
export function formatForMsg(msg) {
return {bstart: 0, bend: 15}
}
export function formatMsgDec(msg) {
const {bstart, bend} = formatForMsg(msg)
const uint = Uint64BE(msg[1])
var tt = "0"+uint.toString(2);
tt = tt.substring(0, tt.length - (63-bend))
tt = tt.substring(tt.length - (bend-bstart) - 1)
return [msg[0], parseInt(tt, 2)];
}
export function uint64BEToHex(int64) {
return Uint64BE(int64).toString(16);
}
export function int64BufferToPrettyHexStr(buffer) {
const uint = Uint64BE(buffer);
let hex = uint.toString(16);
if(hex.length == 1) hex = '0' + hex;
let hexParts = hex.match(/.{1,2}/g);
return hexParts.join(" ")
}
export function formatMsgHex(msg) {
const uint = Uint64BE(msg[1]);
let hex = uint.toString(16);
if(hex.length == 1) hex = '0' + hex;
let hexParts = hex.match(/.{1,2}/g);
return [msg[0], hexParts.join(" ")]
}

View File

@ -0,0 +1,154 @@
const Uint64BE = require('int64-buffer').Uint64BE
const UINT64 = require('cuint').UINT64
import Signal from './signal';
const BO_RE = /^BO\_ (\w+) (\w+) *: (\w+) (\w+)/
// normal signal
const SG_RE = /^SG\_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*)/
// Multiplexed signal
const SGM_RE = /^SG\_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*)/
function floatOrInt(numericStr) {
if(Number.isInteger(numericStr)) {
return parseInt(numericStr);
} else {
return parseFloat(numericStr);
}
}
export function swapOrder(arr, wordSize, gSize) {
const swappedWords = [];
for(let i = 0; i < arr.length; i += wordSize) {
const word = arr.slice(i, i + wordSize);
for(let j = wordSize - gSize; j > -gSize; j -= gSize) {
swappedWords.push(word.slice(j, j + gSize));
}
}
return swappedWords.join("");
}
export default class DBC {
constructor(dbcString) {
this.importDbcString(dbcString);
this.bits = [];
for(let i = 0; i < 64; i += 8) {
for(let j = 7; j > -1; j--) {
this.bits.push(i + j);
}
}
}
getMessageName(msgId) {
const msg = this.messages.get(msgId);
if(msg) return msg.name;
return null;
}
getSignalSpecs(msgId) {
const msg = this.messages.get(msgId);
if(msg) return msg.signals;
return null;
}
importDbcString(dbcString) {
const messages = new Map();
let ids = 0;
dbcString.split('\n').forEach((line, idx) => {
line = line.trim();
if(line.indexOf("BO_") === 0) {
let matches = line.match(BO_RE)
if (matches === null) {
console.log('Bad BO', line)
return
}
let [idString, name, size] = matches.slice(1);
ids = parseInt(idString, 0); // 0 radix parses hex or dec
messages.set(ids, {name, size, signals: {}});
} else if(line.indexOf("SG_") === 0) {
let matches = line.match(SG_RE);
if(matches === null) {
matches = line.match(SGM_RE);
if(matches === null) {
return;
}
// for now, ignore multiplex which is matches[1]
matches = matches[1] + matches.slice(3);
} else {
matches = matches.slice(1)
}
let [name, startBit, size, isLittleEndian, isSigned,
factor, offset, min, max, unit] = matches;
startBit = parseInt(startBit);
size = parseInt(size);
isLittleEndian = parseInt(isLittleEndian) === 1
isSigned = isSigned === '-'
factor = floatOrInt(factor);
offset = floatOrInt(offset);
min = floatOrInt(min);
max = floatOrInt(max);
const signalProperties= {name, startBit, size, isLittleEndian,
isSigned, factor, offset, unit, min, max};
const signal = new Signal(signalProperties);
messages.get(ids).signals[name] = signal;
}
});
this.messages = messages;
}
valueForSignal(signalSpec, hexData) {
const blen = hexData.length * 4;
let value, startBit, dataBitPos;
if (signalSpec.isLittleEndian) {
value = UINT64(swapOrder(hexData, 16, 2), 16);
startBit = signalSpec.startBit;
dataBitPos = UINT64.fromNumber(startBit);
} else {
// big endian
value = UINT64(hexData, 16);
startBit = this.bits.indexOf(signalSpec.startBit);
dataBitPos = UINT64(blen - (startBit + signalSpec.size));
}
// console.log('startBit', startBit)
// console.log('dataBitPos', dataBitPos)
if(dataBitPos < 0) {
return null;
}
let rightHandAnd = UINT64((1 << signalSpec.size) - 1);
let ival = (value.shiftr(dataBitPos)).and(rightHandAnd).toNumber();
if(signalSpec.isSigned && (ival & (1<<(signalSpec.size - 1)))) {
ival -= 1<<signalSpec.size
}
ival = (ival * signalSpec.factor) + signalSpec.offset;
return ival;
}
getSignalValues(messageId, data) {
const hexData = Buffer.from(data).toString('hex');
if(!this.messages.has(messageId)) {
return [];
}
const {signals} = this.messages.get(messageId);
const signalValuesByName = {};
Object.values(signals).forEach((signalSpec) => {
signalValuesByName[signalSpec.name] = this.valueForSignal(signalSpec, hexData);
});
return signalValuesByName;
}
}

View File

@ -0,0 +1,17 @@
export default class Frame {
constructor({name,
id = 0,
dlc = 0,
transmitter = [],
extended = 0,
comment = null,
signals = []}) {
Object.assign(this, {name,
id,
dlc,
transmitter,
extended,
comment,
signals})
}
}

View File

@ -0,0 +1,75 @@
export default class Signal {
constructor({name,
startBit = 0,
size = 0,
isLittleEndian = true,
isSigned = true,
isFloat = false,
factor = 1,
offset = 0,
unit = "",
receiver = [],
comment = null,
multiplex = null,
min = null,
max = null
}) {
Object.assign(this,
{name,
startBit,
size,
isLittleEndian,
isSigned,
isFloat,
factor,
offset,
unit,
receiver,
comment,
multiplex});
if(min == null) {
min = this.calculateMin();
}
if(max == null) {
max = this.calculateMax();
}
Object.assign(this, {min, max});
}
calculateRawRange() {
let rawRange = Math.pow(2, this.size);
if (this.isSigned) {
rawRange /= 2;
}
return [(this.isSigned ? -1 * rawRange : 0),
rawRange - 1]
}
calculateMin() {
const rawMin = this.calculateRawRange()[0];
return this.offset + (rawMin * this.factor);
}
calculateMax() {
const rawMax = this.calculateRawRange()[1];
return this.offset + (rawMax * this.factor);
}
equals(otherSignal) {
return (otherSignal.name == this.name
&& otherSignal.startBit == this.startBit
&& otherSignal.size == this.size
&& otherSignal.isLittleEndian == this.isLittleEndian
&& otherSignal.isSigned == this.isSigned
&& otherSignal.isFloat == this.isFloat
&& otherSignal.factor == this.factor
&& otherSignal.offset == this.offset
&& otherSignal.unit == this.unit
&& otherSignal.receiver.length==this.receiver.length
&& otherSignal.receiver.every((v,i)=> v === this.receiver[i])
&& otherSignal.comment == this.comment
&& otherSignal.multiplex == this.multiplex);
}
}

View File

@ -0,0 +1,5 @@
export const Styles = StyleSheet.create({
root: {
flexDirection: 'row'
},
});

View File

@ -0,0 +1,122 @@
// Client-side parser for .npy files
// See the specification: http://docs.scipy.org/doc/numpy-dev/neps/npy-format.html
const NumpyLoader = (function NumpyLoader() {
function asciiDecode(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
function readUint16LE(buffer) {
var view = new DataView(buffer);
var val = view.getUint8(0);
val |= view.getUint8(1) << 8;
return val;
}
function fromArrayBuffer(buf) {
// Check the magic number
var magic = asciiDecode(buf.slice(0,6));
if (magic !== "\x93NUMPY") {
throw new Error("Bad magic number");
}
var version = new Uint8Array(buf.slice(6,8)),
headerLength = readUint16LE(buf.slice(8,10)),
headerStr = asciiDecode(buf.slice(10, 10+headerLength));
const offsetBytes = 10 + headerLength;
//rest = buf.slice(10+headerLength); XXX -- This makes a copy!!! https://www.khronos.org/registry/typedarray/specs/latest/#5
// Hacky conversion of dict literal string to JS Object
const info = JSON.parse(headerStr.toLowerCase()
.replace('(','[')
.replace('),',']')
.replace(/'/g, '"')
.replace(',]', ']'));
// Intepret the bytes according to the specified dtype
var data;
if (info.descr === "|u1") {
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "|i1") {
data = new Int8Array(buf, offsetBytes);
} else if (info.descr === "<u2") {
data = new Uint16Array(buf, offsetBytes);
} else if (info.descr === "<i2") {
data = new Int16Array(buf, offsetBytes);
} else if (info.descr === "<u4") {
data = new Uint32Array(buf, offsetBytes);
} else if (info.descr === "<i4") {
data = new Int32Array(buf, offsetBytes);
} else if (info.descr === "<f4") {
data = new Float32Array(buf, offsetBytes);
} else if (info.descr === "<f8") {
data = new Float64Array(buf, offsetBytes);
} else if (info.descr === "<u8") {
// 8 byte uint64s
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "<i8") {
// 8 byte int64s
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "|s8") {
// 8 byte strings
data = new Uint8Array(buf, offsetBytes);
} else {
throw new Error('unknown numeric dtype '+info.descr)
}
return {
shape: info.shape,
fortran_order: info.fortran_order,
data: data
};
}
function open(file, callback) {
var reader = new FileReader();
reader.onload = function() {
// the file contents have been read as an array buffer
var buf = reader.result;
var ndarray = fromArrayBuffer(buf);
callback(ndarray);
};
reader.readAsArrayBuffer(file);
}
function promise(url) {
return new Promise(function(resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest();
req.onload = function() {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
var buf = req.response; // not responseText
var ndarray = fromArrayBuffer(buf);
resolve(ndarray);
} else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
reject(Error("Network Error"));
};
// Make the request
req.open('GET', url, true);
req.responseType = "arraybuffer";
req.send(null);
});
}
return {
open: open,
promise: promise,
fromArrayBuffer: fromArrayBuffer,
};
})();
module.exports = NumpyLoader;

View File

@ -0,0 +1,200 @@
import {createClassFromSpec} from 'react-vega';
const canHistogramSpec = {
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
"width": 1000,
"height": 100,
"padding": 5,
"signals": [
{
"name": "segment"
}
],
"data": [
{
"name": "points"
},
{
"name": "binned",
"source": "points",
"transform": [
{
"type": "extent",
"field": "time",
"signal": "extent"
},
{
"type": "bin", "field": "time",
"extent": {"signal": "extent"},
"nice": false
},
{
"type": "aggregate",
"key": "bin0", "groupby": ["bin0", "bin1"],
"fields": ["bin0"], "ops": ["count"], "as": ["count"]
}
]
}
],
"scales": [
{
"name": "xscale",
"type": "linear",
"zero": false,
"range": "width",
"domain": {"data": "points", "field": "time"}
},
{
"name": "yscale",
"type": "linear",
"range": "height", "round": true,
"domain": {"data": "binned", "field": "count"},
"zero": true, "nice": true
}
],
"axes": [
{"orient": "bottom", "scale": "xscale", "zindex": 1},
{"orient": "left", "scale": "yscale", "tickCount": 5, "zindex": 1}
],
"marks": [
{"type": "group",
"name": "histogram",
"encode": {
"enter": {
"height": {"value": 75},
"width": {"value": 1000},
"fill": {"value": "transparent"}
}
},
"signals": [
{
"name": "brush", "value": 0,
"on": [
{
"events": "@histogram:mousedown",
"update": "[x(), x()]"
},
{
"events": "[@histogram:mousedown, window:mouseup] > window:mousemove!",
"update": "[brush[0], clamp(x(), 0, width)]"
},
{
"events": {"signal": "delta"},
"update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
}
]
},
{
"name": "anchor", "value": null,
"on": [{"events": "@brush:mousedown", "update": "slice(brush)"}]
},
{
"name": "xdown", "value": 0,
"on": [{"events": "@brush:mousedown", "update": "x()"}]
},
{
"name": "delta", "value": 0,
"on": [
{
"events": "[@brush:mousedown, window:mouseup] > window:mousemove!",
"update": "x() - xdown"
}
]
},
{
"name": "segment",
"push": "outer",
"on": [
{
"events": {"signal": "brush"},
"update": "span(brush) ? invert('xscale', brush) : null"
}
]
}
],
"marks": [
{
"type": "rect",
"from": {"data": "binned"},
"interactive": false,
"encode": {
"update": {
"x": {"scale": "xscale", "field": "bin0"},
"x2": {"scale": "xscale", "field": "bin1",
"offset": 0},
"y": {"scale": "yscale", "field": "count"},
"y2": {"scale": "yscale", "value": 0},
"fill": {"value": "steelblue"}
}
}
},
{
"type": "rect",
"from": {"data": "points"},
"encode": {
"enter": {
"x": {"scale": "xscale", "field": "time"},
"width": {"value": 1},
"y": {"value": 25, "offset": {"signal": "height"}},
"height": {"value": 5},
"fill": {"value": "steelblue"},
"fillOpacity": {"value": 0.4}
}
}
},
{
"type": "rect",
"name": "brush",
"encode": {
"enter": {
"y": {"value": 0},
"height": {"value": 100},
"fill": {"value": "#333"},
"fillOpacity": {"value": 0.2}
},
"update": {
"x": {"signal": "brush[0]"},
"x2": {"signal": "brush[1]"}
}
}
},
{
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"y": {"value": 0},
"height": {"value": 100},
"width": {"value": 2},
"fill": {"value": "firebrick"}
},
"update": {
"x": {"signal": "brush[0]"}
}
}
},
{
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"y": {"value": 0},
"height": {"value": 100},
"width": {"value": 2},
"fill": {"value": "firebrick"}
},
"update": {
"x": {"signal": "brush[1]"}
}
}
}
]
}
]
};
export default createClassFromSpec(canHistogramSpec);

122
src/vega/CanPlot.js 100644
View File

@ -0,0 +1,122 @@
import {createClassFromSpec} from 'react-vega';
export default createClassFromSpec('CanPlot', {
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
"width": 500,
"height": 200,
"padding": 5,
"signals": [
{
"name": "tipTime",
"on": [{
"events": "mousemove",
"update": "invert('xscale', x())"
}]
},
{"name": "segment", "value": {"data": "table", "field": "x"}}
],
"data": [
{
"name": "table"
},
{
"name": "tooltip",
"source": "table",
"transform": [
{
"type": "filter",
"expr": "abs(datum.x - tipTime) <= 0.1"
},
{
"type": "aggregate",
"fields": ["x", "y", "unit"],
"ops": ["min", "argmin", "argmin"],
"as": ["min", "argmin", "argmin"]
}
]
}
],
"scales": [
{
"name": "xscale",
"type": "linear",
"range": "width",
"zero": false,
"domain": {"data": "table", "field": "x"},
"domainRaw": {"signal": "segment"}
},
{
"name": "yscale",
"type": "linear",
"range": "height",
"zero": true,
"domain": {"data": "table", "field": "y"}
}
],
"axes": [
{"orient": "bottom", "scale": "xscale"},
{"orient": "left", "scale": "yscale"}
],
"marks": [
{
"type": "line",
"from": {"data": "table"},
"interactive": true,
"encode": {
"update": {
"x": {"scale": "xscale", "field": "x"},
"y": {"scale": "yscale", "field": "y"}
},
"hover": {
"fillOpacity": {"value": 0.5}
}
}
},
{
"type": "symbol",
"from": {"data": "tooltip"},
"encode": {
"update": {
"x": {"scale": "xscale", "field": "argmin.x"},
"y": {"scale": "yscale", "field": "argmin.y"},
"size": {"value": 50},
"fill": {"value": "black"}
}
}
},
{
"type": "group",
"from": {"data": "tooltip"},
"interactive": false,
"encode": {
"update": {
"x": {"scale": "xscale", "field": "argmin.x"},
"y": {"scale": "yscale", "field": "argmin.y"},
"height": {"value": 20},
"width": {"value": 150},
"fill": {"value": "#fff"},
"fillOpacity": {"value": 0.85},
"stroke": {"value": "#aaa"},
"strokeWidth": {"value": 0.5}
}
},
"marks": [
{
"type": "text",
"interactive": false,
"encode": {
"update": {
"text": {"signal": "parent.argmin.x + ': ' + parent.argmin.y + ' ' + parent.argmin.unit"},
"fill": {"value": "black"},
"fontWeight": {"value": "bold"}
}
}
}
]
}
]
});