Read thumbnails from rlog files when video isn't working (#28)
* Add rlog thumbnails to cabana * Fixing more linting errorsmain
parent
1be5544df3
commit
5c83305260
34
package.json
34
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",
|
||||
|
|
|
@ -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 (
|
||||
<div
|
||||
id="cabana"
|
||||
|
@ -973,52 +1025,53 @@ export default class CanExplorer extends Component {
|
|||
</div>
|
||||
<div className="cabana-window">
|
||||
<Meta
|
||||
url={this.state.route ? this.state.route.url : null}
|
||||
messages={this.state.messages}
|
||||
selectedMessages={this.state.selectedMessages}
|
||||
url={this.state.route ? route.url : null}
|
||||
messages={messages}
|
||||
selectedMessages={selectedMessages}
|
||||
updateSelectedMessages={this.updateSelectedMessages}
|
||||
showEditMessageModal={this.showEditMessageModal}
|
||||
currentParts={this.state.currentParts}
|
||||
currentParts={currentParts}
|
||||
onMessageSelected={this.onMessageSelected}
|
||||
onMessageUnselected={this.onMessageUnselected}
|
||||
showLoadDbc={this.showLoadDbc}
|
||||
showSaveDbc={this.showSaveDbc}
|
||||
dbcFilename={this.state.dbcFilename}
|
||||
dbcLastSaved={this.state.dbcLastSaved}
|
||||
dbcFilename={dbcFilename}
|
||||
dbcLastSaved={dbcLastSaved}
|
||||
dongleId={this.props.dongleId}
|
||||
name={this.props.name}
|
||||
route={this.state.route}
|
||||
seekTime={this.state.seekTime}
|
||||
seekIndex={this.state.seekIndex}
|
||||
shareUrl={this.state.shareUrl}
|
||||
maxByteStateChangeCount={this.state.maxByteStateChangeCount}
|
||||
route={route}
|
||||
seekTime={seekTime}
|
||||
seekIndex={seekIndex}
|
||||
shareUrl={shareUrl}
|
||||
maxByteStateChangeCount={maxByteStateChangeCount}
|
||||
isDemo={this.props.isDemo}
|
||||
live={this.state.live}
|
||||
live={live}
|
||||
saveLog={debounce(this.downloadLogAsCSV, 500)}
|
||||
/>
|
||||
{this.state.route || this.state.live ? (
|
||||
{route || live ? (
|
||||
<Explorer
|
||||
url={this.state.route ? this.state.route.url : null}
|
||||
live={this.state.live}
|
||||
messages={this.state.messages}
|
||||
selectedMessage={this.state.selectedMessage}
|
||||
url={route ? route.url : null}
|
||||
live={live}
|
||||
messages={messages}
|
||||
thumbnails={thumbnails}
|
||||
selectedMessage={selectedMessage}
|
||||
onConfirmedSignalChange={this.onConfirmedSignalChange}
|
||||
onSeek={this.onSeek}
|
||||
onUserSeek={this.onUserSeek}
|
||||
canFrameOffset={this.state.canFrameOffset}
|
||||
firstCanTime={this.state.firstCanTime}
|
||||
seekTime={this.state.seekTime}
|
||||
seekIndex={this.state.seekIndex}
|
||||
currentParts={this.state.currentParts}
|
||||
selectedPart={this.state.currentPart}
|
||||
partsLoaded={this.state.partsLoaded}
|
||||
canFrameOffset={canFrameOffset}
|
||||
firstCanTime={firstCanTime}
|
||||
seekTime={seekTime}
|
||||
seekIndex={seekIndex}
|
||||
currentParts={currentParts}
|
||||
selectedPart={currentPart}
|
||||
partsLoaded={partsLoaded}
|
||||
autoplay={this.props.autoplay}
|
||||
showEditMessageModal={this.showEditMessageModal}
|
||||
onPartChange={this.onPartChange}
|
||||
routeStartTime={
|
||||
this.state.route ? this.state.route.start_time : Moment()
|
||||
route ? route.start_time : Moment()
|
||||
}
|
||||
partsCount={this.state.route ? this.state.route.proclog : 0}
|
||||
partsCount={route ? route.proclog : 0}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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 (
|
||||
<div className="cabana-explorer">
|
||||
<div className={cx('cabana-explorer-signals', signalsExpandedClass)}>
|
||||
{this.props.messages[this.props.selectedMessage]
|
||||
{messages[this.props.selectedMessage]
|
||||
? this.renderExplorerSignals()
|
||||
: this.renderSelectMessagePrompt()}
|
||||
</div>
|
||||
|
@ -485,7 +487,7 @@ export default class Explorer extends Component {
|
|||
<div className="cabana-explorer-visuals-header g-row" />
|
||||
<br />
|
||||
<RouteVideoSync
|
||||
message={this.props.messages[this.props.selectedMessage]}
|
||||
message={messages[this.props.selectedMessage]}
|
||||
segment={this.state.segment}
|
||||
seekIndex={this.props.seekIndex}
|
||||
userSeekIndex={this.state.userSeekIndex}
|
||||
|
@ -500,6 +502,7 @@ export default class Explorer extends Component {
|
|||
onPause={this.onPause}
|
||||
userSeekTime={this.state.userSeekTime}
|
||||
playSpeed={this.state.playSpeed}
|
||||
thumbnails={thumbnails}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
@ -515,7 +518,7 @@ export default class Explorer extends Component {
|
|||
) : null}
|
||||
<CanGraphList
|
||||
plottedSignals={this.state.plottedSignals}
|
||||
messages={this.props.messages}
|
||||
messages={messages}
|
||||
onGraphTimeClick={this.onGraphTimeClick}
|
||||
seekTime={this.props.seekTime}
|
||||
onSegmentChanged={this.onSegmentChanged}
|
||||
|
|
|
@ -44,21 +44,6 @@ const Styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
export default class RouteVideoSync extends Component {
|
||||
static propTypes = {
|
||||
userSeekIndex: PropTypes.number.isRequired,
|
||||
segment: PropTypes.array.isRequired,
|
||||
message: PropTypes.object,
|
||||
canFrameOffset: PropTypes.number.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
playing: PropTypes.bool.isRequired,
|
||||
onPlaySeek: PropTypes.func.isRequired,
|
||||
onUserSeek: PropTypes.func.isRequired,
|
||||
onPlay: PropTypes.func.isRequired,
|
||||
onPause: PropTypes.func.isRequired,
|
||||
userSeekTime: PropTypes.number.isRequired,
|
||||
playSpeed: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -73,49 +58,71 @@ export default class RouteVideoSync extends Component {
|
|||
this.segmentProgress = this.segmentProgress.bind(this);
|
||||
this.onVideoElementAvailable = this.onVideoElementAvailable.bind(this);
|
||||
this.onUserSeek = this.onUserSeek.bind(this);
|
||||
this.onPlaySeek = this.onPlaySeek.bind(this);
|
||||
this.onHlsRestart = this.onHlsRestart.bind(this);
|
||||
this.ratioTime = this.ratioTime.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
componentDidUpdate(nextProps) {
|
||||
const {
|
||||
userSeekIndex,
|
||||
message,
|
||||
canFrameOffset,
|
||||
userSeekTime
|
||||
} = this.props;
|
||||
const { videoElement } = this.state;
|
||||
if (
|
||||
this.props.userSeekIndex !== nextProps.userSeekIndex
|
||||
|| this.props.canFrameOffset !== nextProps.canFrameOffset
|
||||
|| (this.props.message
|
||||
userSeekIndex !== nextProps.userSeekIndex
|
||||
|| canFrameOffset !== nextProps.canFrameOffset
|
||||
|| (message
|
||||
&& nextProps.message
|
||||
&& this.props.message.entries.length !== nextProps.message.entries.length)
|
||||
&& message.entries.length !== nextProps.message.entries.length)
|
||||
) {
|
||||
this.setState({ shouldRestartHls: true });
|
||||
}
|
||||
if (
|
||||
nextProps.userSeekTime
|
||||
&& this.props.userSeekTime !== nextProps.userSeekTime
|
||||
&& userSeekTime !== nextProps.userSeekTime
|
||||
) {
|
||||
if (this.state.videoElement) {
|
||||
this.state.videoElement.currentTime = nextProps.userSeekTime;
|
||||
if (videoElement) {
|
||||
videoElement.currentTime = nextProps.userSeekTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nearestFrameUrl() {
|
||||
const { url } = this.props;
|
||||
const sec = Math.round(this.props.userSeekTime);
|
||||
if (isNaN(sec)) {
|
||||
debugger;
|
||||
}
|
||||
return RouteApi(url).getJpegUrl(sec);
|
||||
onVideoElementAvailable(videoElement) {
|
||||
this.setState({ videoElement });
|
||||
}
|
||||
|
||||
loadingOverlay() {
|
||||
return (
|
||||
<div className={css(Styles.loadingOverlay)}>
|
||||
<img
|
||||
className={css(Styles.loadingSpinner)}
|
||||
src={`${process.env.PUBLIC_URL}/img/loading.svg`}
|
||||
alt="Loading video"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
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 (
|
||||
<div className={css(Styles.loadingOverlay)}>
|
||||
<img
|
||||
className={css(Styles.loadingSpinner)}
|
||||
src={`${process.env.PUBLIC_URL}/img/loading.svg`}
|
||||
alt="Loading video"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="cabana-explorer-visuals-camera">
|
||||
{this.state.isLoading ? this.loadingOverlay() : null}
|
||||
{this.state.shouldShowJpeg ? (
|
||||
{isLoading ? this.loadingOverlay() : null}
|
||||
{shouldShowJpeg ? (
|
||||
<img
|
||||
src={this.nearestFrameUrl()}
|
||||
className={css(Styles.img)}
|
||||
alt={`Camera preview at t = ${Math.round(this.props.userSeekTime)}`}
|
||||
alt={`Camera preview at t = ${Math.round(userSeekTime)}`}
|
||||
/>
|
||||
) : null}
|
||||
<HLS
|
||||
className={css(Styles.hls)}
|
||||
source={VideoApi(
|
||||
this.props.url,
|
||||
url,
|
||||
process.env.REACT_APP_VIDEO_CDN
|
||||
).getRearCameraStreamIndexUrl()}
|
||||
startTime={this.startTime()}
|
||||
videoLength={this.videoLength()}
|
||||
playbackSpeed={this.props.playSpeed}
|
||||
playbackSpeed={playSpeed}
|
||||
onVideoElementAvailable={this.onVideoElementAvailable}
|
||||
playing={this.props.playing}
|
||||
onClick={this.props.onVideoClick}
|
||||
playing={playing}
|
||||
onClick={onVideoClick}
|
||||
onLoadStart={this.onLoadStart}
|
||||
onLoadEnd={this.onLoadEnd}
|
||||
onUserSeek={this.onUserSeek}
|
||||
onPlaySeek={this.props.onPlaySeek}
|
||||
onPlaySeek={this.onPlaySeek}
|
||||
segmentProgress={this.segmentProgress}
|
||||
shouldRestart={this.state.shouldRestartHls}
|
||||
shouldRestart={shouldRestartHls}
|
||||
onRestart={this.onHlsRestart}
|
||||
/>
|
||||
<RouteSeeker
|
||||
className={css(Styles.seekBar)}
|
||||
nearestFrameTime={this.props.userSeekTime}
|
||||
nearestFrameTime={userSeekTime}
|
||||
segmentProgress={this.segmentProgress}
|
||||
startTime={this.startTime()}
|
||||
videoLength={this.videoLength()}
|
||||
segmentIndices={this.props.segmentIndices}
|
||||
segmentIndices={segmentIndices}
|
||||
onUserSeek={this.onUserSeek}
|
||||
onPlaySeek={this.props.onPlaySeek}
|
||||
onPlaySeek={this.onPlaySeek}
|
||||
videoElement={this.state.videoElement}
|
||||
onPlay={this.props.onPlay}
|
||||
onPause={this.props.onPause}
|
||||
|
@ -245,3 +264,21 @@ export default class RouteVideoSync extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
RouteVideoSync.propTypes = {
|
||||
userSeekIndex: PropTypes.number.isRequired,
|
||||
segment: PropTypes.array.isRequired,
|
||||
message: PropTypes.object,
|
||||
thumbnails: PropTypes.array,
|
||||
canFrameOffset: PropTypes.number.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
playing: PropTypes.bool.isRequired,
|
||||
onPlaySeek: PropTypes.func.isRequired,
|
||||
onUserSeek: PropTypes.func.isRequired,
|
||||
onPlay: PropTypes.func.isRequired,
|
||||
onPause: PropTypes.func.isRequired,
|
||||
userSeekTime: PropTypes.number.isRequired,
|
||||
playSpeed: PropTypes.number.isRequired,
|
||||
onVideoClick: PropTypes.func,
|
||||
segmentIndices: PropTypes.array,
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-env worker */
|
||||
/* eslint-disable no-restricted-globals */
|
||||
/* eslint-disable no-restricted-globals, no-param-reassign */
|
||||
import LogStream from '@commaai/log_reader';
|
||||
import { timeout } from 'thyming';
|
||||
import { partial } from 'ap';
|
||||
|
@ -8,47 +8,25 @@ import { getLogPart } from '../api/rlog';
|
|||
import DbcUtils from '../utils/dbc';
|
||||
import DBC from '../models/can/dbc';
|
||||
import { addressForName } from '../models/can/logSignals';
|
||||
import {
|
||||
getFlags,
|
||||
getUbloxGnss,
|
||||
getEgoData,
|
||||
getCarStateControls,
|
||||
getWheelSpeeds,
|
||||
getThermalFreeSpace,
|
||||
getThermalData,
|
||||
getThermalCPU,
|
||||
getHealth
|
||||
} from './rlog-utils';
|
||||
|
||||
const DEBOUNCE_DELAY = 100;
|
||||
|
||||
self.onmessage = handleMessage;
|
||||
|
||||
function handleMessage(msg) {
|
||||
const options = msg.data;
|
||||
|
||||
if (options.action === 'terminate') {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
options.dbc = new DBC(options.dbcText);
|
||||
|
||||
const entry = new CacheEntry(options);
|
||||
}
|
||||
|
||||
function CacheEntry(options) {
|
||||
options = options || {};
|
||||
this.options = options;
|
||||
|
||||
const {
|
||||
route, part, dbc, logUrls
|
||||
} = options;
|
||||
|
||||
this.messages = {};
|
||||
this.route = route;
|
||||
this.part = part;
|
||||
this.dbc = dbc;
|
||||
this.logUrls = logUrls;
|
||||
this.sendBatch = partial(sendBatch, this);
|
||||
|
||||
// load in the data!
|
||||
loadData(this);
|
||||
}
|
||||
|
||||
function sendBatch(entry) {
|
||||
delete entry.batching;
|
||||
const { messages } = entry;
|
||||
const { messages, thumbnails } = entry;
|
||||
entry.messages = {};
|
||||
entry.thumbnails = [];
|
||||
|
||||
let { maxByteStateChangeCount } = entry.options;
|
||||
const newMaxByteStateChangeCount = DbcUtils.findMaxByteStateChangeCount(
|
||||
|
@ -67,8 +45,9 @@ function sendBatch(entry) {
|
|||
|
||||
self.postMessage({
|
||||
newMessages: messages,
|
||||
maxByteStateChangeCount,
|
||||
isFinished: entry.ended
|
||||
newThumbnails: thumbnails,
|
||||
isFinished: entry.ended,
|
||||
maxByteStateChangeCount
|
||||
});
|
||||
|
||||
if (entry.ended) {
|
||||
|
@ -77,6 +56,93 @@ function sendBatch(entry) {
|
|||
}
|
||||
}
|
||||
|
||||
function queueBatch(entry) {
|
||||
if (!entry.batching) {
|
||||
entry.batching = timeout(entry.sendBatch, DEBOUNCE_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
function getPrevMsgEntry(messages, prevMsgEntries, id) {
|
||||
if (messages[id].entries.length) {
|
||||
return messages[id].entries[messages[id].entries.length - 1];
|
||||
}
|
||||
return prevMsgEntries[id] || null;
|
||||
}
|
||||
|
||||
function insertEventData(src, part, entry, logTime, getData) {
|
||||
const id = `${src}:${part}`;
|
||||
const address = addressForName(id);
|
||||
|
||||
if (!entry.messages[id]) {
|
||||
entry.messages[id] = DbcUtils.createMessageSpec(
|
||||
entry.dbc,
|
||||
address,
|
||||
id,
|
||||
src
|
||||
);
|
||||
entry.messages[id].isLogEvent = true;
|
||||
}
|
||||
const prevMsgEntry = getPrevMsgEntry(
|
||||
entry.messages,
|
||||
entry.options.prevMsgEntries,
|
||||
id
|
||||
);
|
||||
|
||||
const { msgEntry, byteStateChangeCounts } = DbcUtils.parseMessage(
|
||||
entry.dbc,
|
||||
logTime,
|
||||
address,
|
||||
getData(),
|
||||
entry.options.canStartTime,
|
||||
prevMsgEntry
|
||||
);
|
||||
|
||||
entry.messages[id].byteStateChangeCounts = byteStateChangeCounts.map(
|
||||
(count, idx) => 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;
|
||||
|
|
|
@ -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));
|
||||
}
|
39
yarn.lock
39
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"
|
||||
|
|
Loading…
Reference in New Issue