diff --git a/package.json b/package.json
index 62d69cf..b1d35d4 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"homepage": "https://community.comma.ai/cabana",
"dependencies": {
"@commaai/comma-api": "1.1.6",
- "@commaai/log_reader": "^0.3.1",
+ "@commaai/log_reader": "^0.5.3",
"@commaai/my-comma-auth": "^1.1.0",
"@commaai/pandajs": "^0.3.4",
"@craco/craco": "^5.5.0",
@@ -60,6 +60,7 @@
"socket.io-client": "^2.0.3",
"stream-selector": "^0.1.1",
"streamsaver": "^1.0.1",
+ "thyming": "^0.1.1",
"vega": "^5.3.4",
"vega-lite": "^3.0.0",
"vega-tooltip": "^0.4.0"
@@ -96,11 +97,26 @@
"deploy": "npm run build && gh-pages -d build"
},
"lint-staged": {
- "*.{js,jsx}": ["eslint --fix", "git add"],
- "*.json": ["prettier --parser json --write", "git add"],
- "*.{graphql,gql}": ["prettier --parser graphql --write", "git add"],
- "*.{md,markdown}": ["prettier --parser markdown --write", "git add"],
- "*.scss": ["prettier --parser postcss --write", "git add"]
+ "*.{js,jsx}": [
+ "eslint --fix",
+ "git add"
+ ],
+ "*.json": [
+ "prettier --parser json --write",
+ "git add"
+ ],
+ "*.{graphql,gql}": [
+ "prettier --parser graphql --write",
+ "git add"
+ ],
+ "*.{md,markdown}": [
+ "prettier --parser markdown --write",
+ "git add"
+ ],
+ "*.scss": [
+ "prettier --parser postcss --write",
+ "git add"
+ ]
},
"jest": {
"moduleNameMapper": {
@@ -111,7 +127,11 @@
}
},
"browserslist": {
- "production": [">0.2%", "not dead", "not op_mini all"],
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
"development": [
"last 1 chrome version",
"last 1 firefox version",
diff --git a/src/CanExplorer.js b/src/CanExplorer.js
index 7e261df..1fee46b 100644
--- a/src/CanExplorer.js
+++ b/src/CanExplorer.js
@@ -40,21 +40,11 @@ const MessageParser = require('./workers/message-parser.worker.js');
const CanStreamerWorker = require('./workers/CanStreamerWorker.worker.js');
export default class CanExplorer extends Component {
- static propTypes = {
- dongleId: PropTypes.string,
- name: PropTypes.string,
- dbc: PropTypes.instanceOf(DBC),
- dbcFilename: PropTypes.string,
- githubAuthToken: PropTypes.string,
- autoplay: PropTypes.bool,
- max: PropTypes.number,
- url: PropTypes.string
- };
-
constructor(props) {
super(props);
this.state = {
messages: {},
+ thumbnails: [],
selectedMessages: [],
route: null,
canFrameOffset: 0,
@@ -317,6 +307,45 @@ export default class CanExplorer extends Component {
});
}
+ mergeThumbnails(newThumbnails) {
+ const { thumbnails } = this.state;
+ if (!newThumbnails || !newThumbnails.length) {
+ return thumbnails;
+ }
+ if (!thumbnails.length) {
+ return newThumbnails;
+ }
+
+ let oldIndex = 0;
+ let newIndex = 0;
+
+ // is old immediately after new?
+ if (newThumbnails[0].monoTime > thumbnails[thumbnails.length - 1]) {
+ return thumbnails.concat(newThumbnails);
+ }
+ // is new immediately after old?
+ if (newThumbnails[newThumbnails.length - 1] < thumbnails[0]) {
+ return newThumbnails.concat(thumbnails);
+ }
+ let result = [];
+ while (oldIndex < thumbnails.length && newIndex < newThumbnails.length) {
+ if (thumbnails[oldIndex].monoTime < newThumbnails[newIndex].monoTime) {
+ result.push(thumbnails[oldIndex]);
+ oldIndex += 1;
+ } else {
+ result.push(newThumbnails[newIndex]);
+ newIndex += 1;
+ }
+ }
+ if (oldIndex < thumbnails.length) {
+ result = result.concat(thumbnails.slice(oldIndex));
+ } else if (newIndex < newThumbnails.length) {
+ result = result.concat(newThumbnails.slice(newIndex));
+ }
+
+ return result;
+ }
+
addAndRehydrateMessages(newMessages, options) {
// Adds new message entries to messages state
// and "rehydrates" ES6 classes (message frame)
@@ -442,14 +471,13 @@ export default class CanExplorer extends Component {
dbcFilename,
route,
firstCanTime,
- canFrameOffset,
- maxByteStateChangeCount
+ canFrameOffset
} = this.state;
+ let { maxByteStateChangeCount } = this.state;
if (!prevMsgEntries) {
// we have previous messages loaded
const { messages } = this.state;
- const canStartTime = firstCanTime - canFrameOffset;
prevMsgEntries = {};
Object.keys(messages).forEach((key) => {
const { entries } = messages[key];
@@ -485,7 +513,8 @@ export default class CanExplorer extends Component {
return;
}
- let { newMessages, maxByteStateChangeCount, isFinished } = e.data;
+ maxByteStateChangeCount = e.data.maxByteStateChangeCount;
+ const { newMessages, newThumbnails, isFinished } = e.data;
if (maxByteStateChangeCount > this.state.maxByteStateChangeCount) {
this.setState({ maxByteStateChangeCount });
} else {
@@ -497,12 +526,14 @@ export default class CanExplorer extends Component {
maxByteStateChangeCount
);
const prevMsgEntries = {};
- for (const key in newMessages) {
+ Object.keys(newMessages).forEach((key) => {
prevMsgEntries[key] = newMessages[key].entries[newMessages[key].entries.length - 1];
- }
+ });
+
+ const thumbnails = this.mergeThumbnails(newThumbnails);
if (!isFinished) {
- this.setState({ messages });
+ this.setState({ messages, thumbnails });
} else {
const loadingParts = this.state.loadingParts.filter((p) => p !== part);
const loadedParts = [part, ...this.state.loadedParts];
@@ -510,6 +541,7 @@ export default class CanExplorer extends Component {
this.setState(
{
messages,
+ thumbnails,
partsLoaded: this.state.partsLoaded + 1,
loadingParts,
loadedParts
@@ -943,6 +975,26 @@ export default class CanExplorer extends Component {
}
render() {
+ const {
+ route,
+ messages,
+ selectedMessages,
+ currentParts,
+ dbcFilename,
+ dbcLastSaved,
+ seekTime,
+ seekIndex,
+ shareUrl,
+ maxByteStateChangeCount,
+ live,
+ thumbnails,
+ selectedMessage,
+ canFrameOffset,
+ firstCanTime,
+ currentPart,
+ partsLoaded
+ } = this.state;
+
return (
- {this.state.route || this.state.live ? (
+ {route || live ? (
) : null}
@@ -1063,3 +1116,14 @@ export default class CanExplorer extends Component {
);
}
}
+
+CanExplorer.propTypes = {
+ dongleId: PropTypes.string,
+ name: PropTypes.string,
+ dbc: PropTypes.instanceOf(DBC),
+ dbcFilename: PropTypes.string,
+ githubAuthToken: PropTypes.string,
+ autoplay: PropTypes.bool,
+ max: PropTypes.number,
+ url: PropTypes.string
+};
diff --git a/src/__tests__/workers/rlog-utils.test.js b/src/__tests__/workers/rlog-utils.test.js
new file mode 100644
index 0000000..f66956b
--- /dev/null
+++ b/src/__tests__/workers/rlog-utils.test.js
@@ -0,0 +1,61 @@
+/* eslint-env jest */
+import {
+ signedShortToByteArray,
+ shortToByteArray,
+ longToByteArray,
+ signedLongToByteArray,
+ getThermalFlags,
+ // getHealthFlags,
+ // getFlags,
+ // getUbloxGnss,
+ // getEgoData,
+ // getCarStateControls,
+ // getWheelSpeeds,
+ // getThermalFreeSpace,
+ // getThermalData,
+ // getThermalCPU,
+ // getHealth
+} from '../../workers/rlog-utils';
+
+describe('byte array methods', () => {
+ test('signedShortToByteArray', () => {
+ expect(signedShortToByteArray(123)).toMatchObject([0, 123]);
+ expect(signedShortToByteArray(-123)).toMatchObject([255, 133]);
+ });
+ test('shortToByteArray', () => {
+ expect(shortToByteArray(123)).toMatchObject([0, 123]);
+ expect(shortToByteArray(-123)).toMatchObject([255, 133]);
+ });
+ test('longToByteArray', () => {
+ expect(longToByteArray(123)).toMatchObject([0, 0, 0, 123]);
+ expect(longToByteArray(-123)).toMatchObject([255, 255, 255, 133]);
+ });
+ test('signedLongToByteArray', () => {
+ expect(signedLongToByteArray(123)).toMatchObject([0, 0, 0, 123]);
+ expect(signedLongToByteArray(-123)).toMatchObject([255, 255, 255, 133]);
+ });
+});
+
+describe('flags', () => {
+ test('getThermalFlags', () => {
+ expect(getThermalFlags({
+ UsbOnline: false,
+ Started: false
+ })).toBe(0x00);
+
+ expect(getThermalFlags({
+ UsbOnline: true,
+ Started: false
+ })).toBe(0x01);
+
+ expect(getThermalFlags({
+ UsbOnline: false,
+ Started: true
+ })).toBe(0x02);
+
+ expect(getThermalFlags({
+ UsbOnline: true,
+ Started: true
+ })).toBe(0x03);
+ });
+});
diff --git a/src/components/Explorer.js b/src/components/Explorer.js
index 2096aba..d59d0a8 100644
--- a/src/components/Explorer.js
+++ b/src/components/Explorer.js
@@ -460,6 +460,8 @@ export default class Explorer extends Component {
? 'is-expanded'
: null;
+ const { thumbnails, messages } = this.props;
+
let graphSegment = this.state.segment;
if (!graphSegment.length && this.props.currentParts) {
graphSegment = [
@@ -471,7 +473,7 @@ export default class Explorer extends Component {
return (
- {this.props.messages[this.props.selectedMessage]
+ {messages[this.props.selectedMessage]
? this.renderExplorerSignals()
: this.renderSelectMessagePrompt()}
@@ -485,7 +487,7 @@ export default class Explorer extends Component {
) : null}
@@ -515,7 +518,7 @@ export default class Explorer extends Component {
) : null}
-
-
- );
+ onUserSeek(ratio) {
+ /* ratio in [0,1] */
+
+ const { videoElement } = this.state;
+ const { onUserSeek } = this.props;
+ const seekTime = this.ratioTime(ratio);
+ const funcSeekToRatio = () => onUserSeek(seekTime);
+
+ if (Number.isNaN(videoElement.duration)) {
+ this.setState({ shouldRestartHls: true }, funcSeekToRatio);
+ return;
+ }
+ videoElement.currentTime = seekTime;
+
+ if (ratio === 0) {
+ this.setState({ shouldRestartHls: true }, funcSeekToRatio);
+ } else {
+ funcSeekToRatio();
+ }
+ }
+
+ onHlsRestart() {
+ this.setState({ shouldRestartHls: false });
+ }
+
+ onPlaySeek(offset) {
+ const { onPlaySeek } = this.props;
+ this.seekTime = offset;
+ onPlaySeek(offset);
}
onLoadStart() {
@@ -132,6 +139,18 @@ export default class RouteVideoSync extends Component {
});
}
+ loadingOverlay() {
+ return (
+
+
![Loading video]({`${process.env.PUBLIC_URL}/img/loading.svg`})
+
+ );
+ }
+
videoLength() {
if (this.props.segment.length) {
return this.props.segment[1] - this.props.segment[0];
@@ -168,73 +187,73 @@ export default class RouteVideoSync extends Component {
return ratio * this.videoLength() + this.startTime();
}
- onVideoElementAvailable(videoElement) {
- this.setState({ videoElement });
- }
-
- onUserSeek(ratio) {
- /* ratio in [0,1] */
-
- const { videoElement } = this.state;
- if (isNaN(videoElement.duration)) {
- this.setState({ shouldRestartHls: true }, funcSeekToRatio);
- return;
+ nearestFrameUrl() {
+ const { thumbnails } = this.props;
+ if (!this.seekTime) {
+ return '';
}
- const seekTime = this.ratioTime(ratio);
- videoElement.currentTime = seekTime;
-
- const funcSeekToRatio = () => this.props.onUserSeek(seekTime);
- if (ratio === 0) {
- this.setState({ shouldRestartHls: true }, funcSeekToRatio);
- } else {
- funcSeekToRatio();
+ for (let i = 0, l = thumbnails.length; i < l; ++i) {
+ if (Math.abs(thumbnails[i].monoTime - this.seekTime) < 5) {
+ const data = btoa(String.fromCharCode(...thumbnails[i].data));
+ return `data:image/jpeg;base64,${data}`;
+ }
}
- }
-
- onHlsRestart() {
- this.setState({ shouldRestartHls: false });
+ return '';
}
render() {
+ const {
+ isLoading,
+ shouldRestartHls,
+ shouldShowJpeg
+ } = this.state;
+ const {
+ userSeekTime,
+ url,
+ playSpeed,
+ playing,
+ onVideoClick,
+ segmentIndices
+ } = this.props;
return (
- {this.state.isLoading ? this.loadingOverlay() : null}
- {this.state.shouldShowJpeg ? (
+ {isLoading ? this.loadingOverlay() : null}
+ {shouldShowJpeg ? (
![{`Camera]({this.nearestFrameUrl()})
) : null}
entry.messages[id].byteStateChangeCounts[idx] + count
+ );
+
+ entry.messages[id].entries.push(msgEntry);
+}
+
+function insertCanMessage(entry, logTime, msg) {
+ const src = msg.Src;
+ const address = Number(msg.Address);
+ const addressHexStr = address.toString(16);
+ const id = `${src}:${addressHexStr}`;
+
+ if (!entry.messages[id]) {
+ entry.messages[id] = DbcUtils.createMessageSpec(
+ entry.dbc,
+ address,
+ id,
+ src
+ );
+ entry.messages[id].isLogEvent = false;
+ }
+ const prevMsgEntry = getPrevMsgEntry(
+ entry.messages,
+ entry.options.prevMsgEntries,
+ id
+ );
+
+ const { msgEntry, byteStateChangeCounts } = DbcUtils.parseMessage(
+ entry.dbc,
+ logTime,
+ address,
+ msg.Dat,
+ entry.options.canStartTime,
+ prevMsgEntry
+ );
+
+ entry.messages[id].byteStateChangeCounts = byteStateChangeCounts.map(
+ (count, idx) => entry.messages[id].byteStateChangeCounts[idx] + count
+ );
+
+ entry.messages[id].entries.push(msgEntry);
+
+ // console.log(id);
+}
+
async function loadData(entry) {
let url = null;
@@ -85,9 +151,10 @@ async function loadData(entry) {
}
if (!url || url.indexOf('.7z') !== -1) {
- return self.postMessage({
+ self.postMessage({
error: 'Invalid or missing log files'
});
+ return;
}
const res = await getLogPart(entry.logUrls[entry.part]);
const logReader = new LogStream(res);
@@ -102,10 +169,6 @@ async function loadData(entry) {
});
});
- const msgArr = [];
- const startTime = Date.now();
- const i = 0;
-
logReader((msg) => {
if (entry.ended) {
console.log('You can get msgs after end', msg);
@@ -191,290 +254,49 @@ async function loadData(entry) {
monoTime,
partial(getThermalFreeSpace, msg.Thermal)
);
+ } else if ('Thumbnail' in msg) {
+ const monoTime = msg.LogMonoTime / 1000000000 - entry.options.canStartTime;
+ const data = new Uint8Array(msg.Thumbnail.Thumbnail);
+ entry.thumbnails.push({ data, monoTime });
} else {
+ // console.log(Object.keys(msg));
return;
}
queueBatch(entry);
});
}
-function queueBatch(entry) {
- if (!entry.batching) {
- entry.batching = timeout(entry.sendBatch, DEBOUNCE_DELAY);
- }
+function CacheEntry(options) {
+ options = options || {};
+ this.options = options;
+
+ const {
+ route, part, dbc, logUrls
+ } = options;
+
+ this.messages = {};
+ this.thumbnails = [];
+ this.route = route;
+ this.part = part;
+ this.dbc = dbc;
+ this.logUrls = logUrls;
+ this.sendBatch = partial(sendBatch, this);
+ this.loadData = partial(loadData, this);
}
-function insertEventData(src, part, entry, logTime, getData) {
- const id = `${src}:${part}`;
- const address = addressForName(id);
+function handleMessage(msg) {
+ const options = msg.data;
- if (!entry.messages[id]) {
- entry.messages[id] = DbcUtils.createMessageSpec(
- entry.dbc,
- address,
- id,
- src
- );
- entry.messages[id].isLogEvent = true;
+ if (options.action === 'terminate') {
+ close();
+ return;
}
- const prevMsgEntry = getPrevMsgEntry(
- entry.messages,
- entry.options.prevMsgEntries,
- id
- );
- const { msgEntry, byteStateChangeCounts } = DbcUtils.parseMessage(
- entry.dbc,
- logTime,
- address,
- getData(),
- entry.options.canStartTime,
- prevMsgEntry
- );
+ options.dbc = new DBC(options.dbcText);
- entry.messages[id].byteStateChangeCounts = byteStateChangeCounts.map(
- (count, idx) => entry.messages[id].byteStateChangeCounts[idx] + count
- );
-
- entry.messages[id].entries.push(msgEntry);
+ const entry = new CacheEntry(options);
+ // load in the data!
+ entry.loadData();
}
-function getThermalFlags(state) {
- let flags = 0x00;
-
- if (state.UsbOnline) {
- flags |= 0x01;
- }
- if (state.Started) {
- flags |= 0x02;
- }
-
- return flags;
-}
-
-function getThermalFreeSpace(state) {
- return longToByteArray(state.FreeSpace * 1000000000);
-}
-
-function getThermalData(state) {
- return shortToByteArray(state.Mem)
- .concat(shortToByteArray(state.Gpu))
- .concat(shortToByteArray(state.FanSpeed))
- .concat(state.BatteryPercent)
- .concat(getThermalFlags(state));
-}
-
-function getThermalCPU(state) {
- return shortToByteArray(state.Cpu0)
- .concat(shortToByteArray(state.Cpu1))
- .concat(shortToByteArray(state.Cpu2))
- .concat(shortToByteArray(state.Cpu3));
-}
-
-function getHealth(state) {
- return signedShortToByteArray(state.Voltage)
- .concat(state.Current)
- .concat(getHealthFlags(state));
-}
-
-function getHealthFlags(state) {
- let flags = 0x00;
-
- if (state.Started) {
- flags |= 0x01;
- }
- if (state.ControlsAllowed) {
- flags |= 0x02;
- }
- if (state.GasInterceptorDetected) {
- flags |= 0x04;
- }
- if (state.StartedSignalDetected) {
- flags |= 0x08;
- }
-
- return flags;
-}
-
-function getUbloxGnss(state) {
- return signedLongToByteArray(state.RcvTow / 1000)
- .concat(signedShortToByteArray(state.GpsWeek))
- .concat([state.LeapSeconds])
- .concat([state.NumMeas]);
-}
-
-function getEgoData(state) {
- return signedShortToByteArray(state.VEgo * 1000)
- .concat(signedShortToByteArray(state.AEgo * 1000))
- .concat(signedShortToByteArray(state.VEgoRaw * 1000))
- .concat(signedShortToByteArray(state.YawRate * 1000));
-}
-
-function getCarStateControls(state) {
- return signedLongToByteArray(state.SteeringAngle * 1000)
- .concat(signedShortToByteArray(state.Brake * 1000))
- .concat(signedShortToByteArray(state.Gas * 1000));
-}
-
-function getWheelSpeeds(state) {
- return signedShortToByteArray(state.WheelSpeeds.Fl * 100)
- .concat(signedShortToByteArray(state.WheelSpeeds.Fr * 100))
- .concat(signedShortToByteArray(state.WheelSpeeds.Rl * 100))
- .concat(signedShortToByteArray(state.WheelSpeeds.Rr * 100));
-}
-
-function getFlags(state) {
- let flags = 0x00;
- const arr = [0, 0, 0];
-
- if (state.LeftBlinker) {
- flags |= 0x01;
- }
- if (state.RightBlinker) {
- flags |= 0x02;
- }
- if (state.GenericToggle) {
- flags |= 0x04;
- }
- if (state.DoorOpen) {
- flags |= 0x08;
- }
- if (state.SeatbeltUnlatched) {
- flags |= 0x10;
- }
- if (state.GasPressed) {
- flags |= 0x20;
- }
- if (state.BrakeLights) {
- flags |= 0x40;
- }
- if (state.SteeringPressed) {
- flags |= 0x80;
- }
-
- arr[0] = flags;
- flags = 0x00;
-
- if (state.Standstill) {
- flags |= 0x01;
- }
- if (state.CruiseState.Enabled) {
- flags |= 0x02;
- }
- if (state.CruiseState.Available) {
- flags |= 0x04;
- }
- if (state.CruiseState.Standstill) {
- flags |= 0x08;
- }
- if (state.GearShifter) {
- flags |= state.GearShifter << 4;
- }
-
- arr[1] = flags;
- arr[2] = state.CruiseState.Speed;
- return arr;
-}
-
-function insertCanMessage(entry, logTime, msg) {
- const src = msg.Src;
- const address = Number(msg.Address);
- const busTime = msg.BusTime;
- const addressHexStr = address.toString(16);
- const id = `${src}:${addressHexStr}`;
-
- if (!entry.messages[id]) {
- entry.messages[id] = DbcUtils.createMessageSpec(
- entry.dbc,
- address,
- id,
- src
- );
- entry.messages[id].isLogEvent = false;
- }
- const prevMsgEntry = getPrevMsgEntry(
- entry.messages,
- entry.options.prevMsgEntries,
- id
- );
-
- const { msgEntry, byteStateChangeCounts } = DbcUtils.parseMessage(
- entry.dbc,
- logTime,
- address,
- msg.Dat,
- entry.options.canStartTime,
- prevMsgEntry
- );
-
- entry.messages[id].byteStateChangeCounts = byteStateChangeCounts.map(
- (count, idx) => entry.messages[id].byteStateChangeCounts[idx] + count
- );
-
- entry.messages[id].entries.push(msgEntry);
-
- // console.log(id);
-}
-
-function getPrevMsgEntry(messages, prevMsgEntries, id) {
- if (messages[id].entries.length) {
- return messages[id].entries[messages[id].entries.length - 1];
- }
- return prevMsgEntries[id] || null;
-}
-
-function signedShortToByteArray(short) {
- const byteArray = [0, 0];
- const isNegative = short < 0;
- if (isNegative) {
- short += Math.pow(2, 8 * byteArray.length);
- }
-
- for (let index = byteArray.length - 1; index >= 0; --index) {
- const byte = short & 0xff;
- byteArray[index] = byte;
- short >>= 8;
- }
-
- return byteArray;
-}
-
-function shortToByteArray(short) {
- const byteArray = [0, 0];
-
- for (let index = byteArray.length - 1; index >= 0; --index) {
- const byte = short & 0xff;
- byteArray[index] = byte;
- short >>= 8;
- }
-
- return byteArray;
-}
-
-function longToByteArray(long) {
- const byteArray = [0, 0, 0, 0];
-
- for (let index = byteArray.length - 1; index >= 0; --index) {
- const byte = long & 0xff;
- byteArray[index] = byte;
- long >>= 8;
- }
-
- return byteArray;
-}
-
-function signedLongToByteArray(long) {
- const byteArray = [0, 0, 0, 0];
- const isNegative = long < 0;
- if (isNegative) {
- long += Math.pow(2, 8 * byteArray.length);
- }
-
- for (let index = byteArray.length - 1; index >= 0; --index) {
- const byte = long & 0xff;
- byteArray[index] = byte;
- long >>= 8;
- }
-
- return byteArray;
-}
+self.onmessage = handleMessage;
diff --git a/src/workers/rlog-utils.js b/src/workers/rlog-utils.js
new file mode 100644
index 0000000..12c89ac
--- /dev/null
+++ b/src/workers/rlog-utils.js
@@ -0,0 +1,194 @@
+/* eslint-disable no-param-reassign, no-bitwise */
+
+export function signedShortToByteArray(short) {
+ const byteArray = [0, 0];
+ const isNegative = short < 0;
+ if (isNegative) {
+ short += 2 ** (8 * byteArray.length);
+ }
+
+ for (let index = byteArray.length - 1; index >= 0; --index) {
+ const byte = short & 0xff;
+ byteArray[index] = byte;
+ short >>= 8;
+ }
+
+ return byteArray;
+}
+
+export function shortToByteArray(short) {
+ const byteArray = [0, 0];
+
+ for (let index = byteArray.length - 1; index >= 0; --index) {
+ const byte = short & 0xff;
+ byteArray[index] = byte;
+ short >>= 8;
+ }
+
+ return byteArray;
+}
+
+export function longToByteArray(long) {
+ const byteArray = [0, 0, 0, 0];
+
+ for (let index = byteArray.length - 1; index >= 0; --index) {
+ const byte = long & 0xff;
+ byteArray[index] = byte;
+ long >>= 8;
+ }
+
+ return byteArray;
+}
+
+export function signedLongToByteArray(long) {
+ const byteArray = [0, 0, 0, 0];
+ const isNegative = long < 0;
+ if (isNegative) {
+ long += 2 ** (8 * byteArray.length);
+ }
+
+ for (let index = byteArray.length - 1; index >= 0; --index) {
+ const byte = long & 0xff;
+ byteArray[index] = byte;
+ long >>= 8;
+ }
+
+ return byteArray;
+}
+
+export function getThermalFlags(state) {
+ let flags = 0x00;
+
+ if (state.UsbOnline) {
+ flags |= 0x01;
+ }
+ if (state.Started) {
+ flags |= 0x02;
+ }
+
+ return flags;
+}
+
+export function getHealthFlags(state) {
+ let flags = 0x00;
+
+ if (state.Started) {
+ flags |= 0x01;
+ }
+ if (state.ControlsAllowed) {
+ flags |= 0x02;
+ }
+ if (state.GasInterceptorDetected) {
+ flags |= 0x04;
+ }
+ if (state.StartedSignalDetected) {
+ flags |= 0x08;
+ }
+
+ return flags;
+}
+
+export function getFlags(state) {
+ let flags = 0x00;
+ const arr = [0, 0, 0];
+
+ if (state.LeftBlinker) {
+ flags |= 0x01;
+ }
+ if (state.RightBlinker) {
+ flags |= 0x02;
+ }
+ if (state.GenericToggle) {
+ flags |= 0x04;
+ }
+ if (state.DoorOpen) {
+ flags |= 0x08;
+ }
+ if (state.SeatbeltUnlatched) {
+ flags |= 0x10;
+ }
+ if (state.GasPressed) {
+ flags |= 0x20;
+ }
+ if (state.BrakeLights) {
+ flags |= 0x40;
+ }
+ if (state.SteeringPressed) {
+ flags |= 0x80;
+ }
+
+ arr[0] = flags;
+ flags = 0x00;
+
+ if (state.Standstill) {
+ flags |= 0x01;
+ }
+ if (state.CruiseState.Enabled) {
+ flags |= 0x02;
+ }
+ if (state.CruiseState.Available) {
+ flags |= 0x04;
+ }
+ if (state.CruiseState.Standstill) {
+ flags |= 0x08;
+ }
+ if (state.GearShifter) {
+ flags |= state.GearShifter << 4;
+ }
+
+ arr[1] = flags;
+ arr[2] = state.CruiseState.Speed;
+ return arr;
+}
+
+export function getUbloxGnss(state) {
+ return signedLongToByteArray(state.RcvTow / 1000)
+ .concat(signedShortToByteArray(state.GpsWeek))
+ .concat([state.LeapSeconds])
+ .concat([state.NumMeas]);
+}
+
+export function getEgoData(state) {
+ return signedShortToByteArray(state.VEgo * 1000)
+ .concat(signedShortToByteArray(state.AEgo * 1000))
+ .concat(signedShortToByteArray(state.VEgoRaw * 1000))
+ .concat(signedShortToByteArray(state.YawRate * 1000));
+}
+
+export function getCarStateControls(state) {
+ return signedLongToByteArray(state.SteeringAngle * 1000)
+ .concat(signedShortToByteArray(state.Brake * 1000))
+ .concat(signedShortToByteArray(state.Gas * 1000));
+}
+
+export function getWheelSpeeds(state) {
+ return signedShortToByteArray(state.WheelSpeeds.Fl * 100)
+ .concat(signedShortToByteArray(state.WheelSpeeds.Fr * 100))
+ .concat(signedShortToByteArray(state.WheelSpeeds.Rl * 100))
+ .concat(signedShortToByteArray(state.WheelSpeeds.Rr * 100));
+}
+
+export function getThermalFreeSpace(state) {
+ return longToByteArray(state.FreeSpace * 1000000000);
+}
+
+export function getThermalData(state) {
+ return shortToByteArray(state.Mem)
+ .concat(shortToByteArray(state.Gpu))
+ .concat(shortToByteArray(state.FanSpeed))
+ .concat(state.BatteryPercent)
+ .concat(getThermalFlags(state));
+}
+
+export function getThermalCPU(state) {
+ return shortToByteArray(state.Cpu0)
+ .concat(shortToByteArray(state.Cpu1))
+ .concat(shortToByteArray(state.Cpu2))
+ .concat(shortToByteArray(state.Cpu3));
+}
+
+export function getHealth(state) {
+ return signedShortToByteArray(state.Voltage)
+ .concat(state.Current)
+ .concat(getHealthFlags(state));
+}
diff --git a/yarn.lock b/yarn.lock
index ad5e536..32c27ca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -911,6 +911,13 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
+"@commaai/capnp-json@^0.2.0":
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/@commaai/capnp-json/-/capnp-json-0.2.0.tgz#866bba39274b02f899803dc7968a7fe79fb02321"
+ integrity sha512-PXqEjvoLkb7VPaeMlFV7NspHAUMfTGhd6plQ72rilrjagf1os73fdItdlhk/q2XELx+l/Vq1xRSLJT1Os0DVqQ==
+ dependencies:
+ capnp-ts "0.2.4"
+
"@commaai/comma-api@1.1.6":
version "1.1.6"
resolved "https://registry.yarnpkg.com/@commaai/comma-api/-/comma-api-1.1.6.tgz#be486443a8d8843c74a811cdd406b85694b5d526"
@@ -931,21 +938,21 @@
joi-browser "^13.4.0"
querystringify "^2.1.1"
-"@commaai/log_reader@^0.3.1":
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/@commaai/log_reader/-/log_reader-0.3.1.tgz#0e2b47b621ac9b852ccf5137787e17952a34170c"
- integrity sha512-WMV6BiSBOfPFOld3KKuuEHRSAPv95cpeDes4LWwy4cONYZHnnuVu91v5nYzQVbB3F6eQaCvkakmwLSzR2UQkow==
+"@commaai/log_reader@^0.5.3":
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/@commaai/log_reader/-/log_reader-0.5.3.tgz#c3dd2c6f3b911198c32bbebd34d0e9cc862e0672"
+ integrity sha512-/962u7s7uXPMGLz2AH+L57w+CSXCX1uZmh0OLSwGZXzyxO/dGvimaVspYe9N78Q0BYa1B5ctv986zliaAblrmg==
dependencies:
- "@commaai/unbzip2-stream" "^2.0.0"
+ "@commaai/capnp-json" "^0.2.0"
JSONStream "^1.3.2"
ap "^0.2.0"
- capnp-json "^0.1.2"
capnp-split "^0.1.1"
capnp-ts "^0.2.4"
commander "^2.15.1"
file-type "^7.6.0"
geval "^2.2.0"
stream-selector "^0.4.0"
+ wasm-bz2 "^0.0.2"
"@commaai/my-comma-auth@^1.1.0":
version "1.1.3"
@@ -978,11 +985,6 @@
optionalDependencies:
usb "^1.3.1"
-"@commaai/unbzip2-stream@^2.0.0":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@commaai/unbzip2-stream/-/unbzip2-stream-2.0.0.tgz#93821dc5e72f04ec5eedef5ed4e6ff57412019e0"
- integrity sha512-Dqzzmos7r2OsvpJ9YtID1n50GpVwB4dw7ft41XJZM2V7bJ0L68ygeK4WktaT1ZslgWz5NzqHEn0t9nWFuopixg==
-
"@craco/craco@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@craco/craco/-/craco-5.5.0.tgz#081b25522d866fbc14b80fe61517f2f10e3e4499"
@@ -2830,11 +2832,6 @@ canvas@^1.6:
dependencies:
nan "^2.10.0"
-capnp-json@^0.1.2:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/capnp-json/-/capnp-json-0.1.3.tgz#e305dd690b9fcf53e2e19278eb6a99d187ea8047"
- integrity sha512-S1bRHkHECsFVjETOZKnxIeab00ozGb7wyS7HEkNhOLUo1/9oDvcURdttp7AbOPDUSIUWM9HoPKdnnH/SSHNBWA==
-
capnp-split@^0.1.1:
version "0.1.4"
resolved "https://registry.yarnpkg.com/capnp-split/-/capnp-split-0.1.4.tgz#c3f86890645cd203c5ce23ff1ccb4fe6642ea4a3"
@@ -2842,7 +2839,7 @@ capnp-split@^0.1.1:
dependencies:
through2 "^2.0.3"
-capnp-ts@^0.2.4:
+capnp-ts@0.2.4, capnp-ts@^0.2.4:
version "0.2.4"
resolved "https://registry.yarnpkg.com/capnp-ts/-/capnp-ts-0.2.4.tgz#da729493311f384d65d480d9afe979ceab9f41bb"
integrity sha512-A9+Awl2WQDhg0fpEoyDpIF7RUQp27gpYBLRGV2zKA37a0IBEutdgIKDI7pO44C9AhzxvCfR6Ooj5W14D3TKaQA==
@@ -13330,6 +13327,14 @@ walker@^1.0.7, walker@~1.0.5:
dependencies:
makeerror "1.0.x"
+wasm-bz2@^0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/wasm-bz2/-/wasm-bz2-0.0.2.tgz#ff5c4e3ed660169ec14f00bd9f4650c43c142a13"
+ integrity sha512-OjWSwiIvz8yacBBqajiZTSVI9Yom1nHFjBuXJFTOwa9rkHIQFNmFaEG65kmnUlFMUNU6mxmGGKAwF8BGMesZ6g==
+ dependencies:
+ ap "^0.2.0"
+ weakmap-event "^2.0.7"
+
watchpack@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"