From 1b59a46959b5a5f4a058f063ee7fb510d7e446b3 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Fri, 29 May 2026 11:25:55 -0700 Subject: [PATCH 1/8] Cache body RPC fields --- src/actions/index.js | 31 ++++++++++++++++++++++++- src/actions/types.js | 1 + src/components/DeviceInfo/index.jsx | 35 +++++------------------------ src/reducers/globalState.js | 33 +++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index a44d3bfe3..a3d630acb 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -7,7 +7,7 @@ import MyCommaAuth from '@commaai/my-comma-auth'; import * as Types from './types'; import { resetPlayback, selectLoop } from '../timeline/playback'; import {hasRoutesData } from '../timeline/segments'; -import { getDeviceFromState, deviceVersionAtLeast } from '../utils'; +import { getDeviceFromState, deviceVersionAtLeast, deviceIsOnline } from '../utils'; let routesRequest = null; let routesRequestPromise = null; @@ -415,6 +415,35 @@ export function fetchDeviceNetworkStatus(dongleId) { }; } +export function fetchDeviceNotCar(dongleId) { + return async (dispatch, getState) => { + const device = getDeviceFromState(getState(), dongleId); + if (!deviceIsOnline(device)) { + return; + } + const payload = { + id: 0, + jsonrpc: '2.0', + method: 'getNotCar', + }; + try { + const resp = await Athena.postJsonRpcPayload(dongleId, payload); + if (resp && resp.result !== undefined) { + dispatch({ + type: Types.ACTION_UPDATE_DEVICE_RPC, + dongleId, + fields: { not_car: resp.result === true }, + }); + } + } catch (err) { + if (!err.message || err.message.indexOf('Device not registered') === -1) { + console.error(err); + Sentry.captureException(err, { fingerprint: 'athena_fetch_notcar' }); + } + } + }; +} + export function updateDevices(devices) { return { type: Types.ACTION_UPDATE_DEVICES, diff --git a/src/actions/types.js b/src/actions/types.js index b9dc4aa11..06a4490e1 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -16,6 +16,7 @@ export const ACTION_UPDATE_ROUTE_LOCATION = 'ACTION_UPDATE_ROUTE_LOCATION'; export const ACTION_UPDATE_SHARED_DEVICE = 'ACTION_UPDATE_SHARED_DEVICE'; export const ACTION_UPDATE_DEVICE_ONLINE = 'ACTION_UPDATE_DEVICE_ONLINE'; export const ACTION_UPDATE_DEVICE_NETWORK = 'ACTION_UPDATE_DEVICE_NETWORK'; +export const ACTION_UPDATE_DEVICE_RPC = 'ACTION_UPDATE_DEVICE_RPC'; export const ACTION_PRIME_NAV = 'ACTION_PRIME_NAV'; export const ACTION_PRIME_SUBSCRIPTION = 'ACTION_PRIME_SUBSCRIPTION'; export const ACTION_PRIME_SUBSCRIBE_INFO = 'ACTION_PRIME_SUBSCRIBE_INFO'; diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx index 9ed3ca4e7..9dda12c56 100644 --- a/src/components/DeviceInfo/index.jsx +++ b/src/components/DeviceInfo/index.jsx @@ -8,7 +8,7 @@ import { withStyles, Typography, Button, CircularProgress, Popper, Tooltip } fro import AccessTime from '@material-ui/icons/AccessTime'; import { athena as Athena, devices as Devices } from '@commaai/api'; -import { analyticsEvent, primeNav } from '../../actions'; +import { analyticsEvent, primeNav, fetchDeviceNotCar } from '../../actions'; import Colors from '../../colors'; import { GamepadIcon } from '../../icons'; import { deviceNamePretty, deviceIsOnline, deviceVersionAtLeast } from '../../utils'; @@ -207,7 +207,6 @@ class DeviceInfo extends Component { snapshot: {}, windowWidth: window.innerWidth, isTimeSelectOpen: false, - isCommaBody: false, bodyTeleopOpen: false, }; @@ -217,7 +216,6 @@ class DeviceInfo extends Component { this.onVisible = this.onVisible.bind(this); this.fetchDeviceInfo = this.fetchDeviceInfo.bind(this); this.fetchDeviceCarHealth = this.fetchDeviceCarHealth.bind(this); - this.fetchIsNotCar = this.fetchIsNotCar.bind(this); this.takeSnapshot = this.takeSnapshot.bind(this); this.snapshotType = this.snapshotType.bind(this); this.renderButtons = this.renderButtons.bind(this); @@ -250,7 +248,6 @@ class DeviceInfo extends Component { carHealth: {}, snapshot: {}, windowWidth: window.innerWidth, - isCommaBody: false, }); } } @@ -264,11 +261,11 @@ class DeviceInfo extends Component { } onVisible() { - const { device } = this.props; + const { device, dongleId } = this.props; if (!device.shared) { this.fetchDeviceInfo(); this.fetchDeviceCarHealth(); - this.fetchIsNotCar(); + this.props.dispatch(fetchDeviceNotCar(dongleId)); } } @@ -320,29 +317,6 @@ class DeviceInfo extends Component { } } - async fetchIsNotCar() { - const { dongleId, device } = this.props; - if (!deviceIsOnline(device)) { - return; - } - - try { - const payload = { - method: 'getNotCar', - jsonrpc: '2.0', - id: 0, - }; - const resp = await Athena.postJsonRpcPayload(dongleId, payload); - if (this.mounted && dongleId === this.props.dongleId) { - this.setState({ isCommaBody: resp.result === true }); - } - } catch (err) { - if (!err.message || err.message.indexOf('Device not registered') === -1) { - console.error(err); - } - } - } - async takeSnapshot() { const { dongleId } = this.props; const { snapshot } = this.state; @@ -518,7 +492,8 @@ class DeviceInfo extends Component { renderButtons() { const { classes, device } = this.props; - const { snapshot, carHealth, windowWidth, isTimeSelectOpen, isCommaBody } = this.state; + const { snapshot, carHealth, windowWidth, isTimeSelectOpen } = this.state; + const isCommaBody = device?.rpc?.not_car; let batteryVoltage; let batteryBackground = Colors.grey400; diff --git a/src/reducers/globalState.js b/src/reducers/globalState.js index e0a394c38..4e74fd415 100644 --- a/src/reducers/globalState.js +++ b/src/reducers/globalState.js @@ -107,6 +107,11 @@ export default function reducer(_state, action) { state = { ...state, devices: action.devices + .map((d) => { + // `rpc` holds Athena RPC-fetched values that would be wiped by listDevices payload + const prev = (_state.devices || []).find((p) => p.dongle_id === d.dongle_id); + return prev && prev.rpc ? { ...d, rpc: prev.rpc } : d; + }) .map(populateFetchedAt) .sort(deviceCompareFn), }; @@ -251,6 +256,34 @@ export default function reducer(_state, action) { }; } break; + case Types.ACTION_UPDATE_DEVICE_RPC: + // merge RPC-fetched values (e.g. not_car) into a specific device's `rpc` field + state = { + ...state, + devices: [...state.devices], + }; + deviceIndex = state.devices.findIndex((d) => d.dongle_id === action.dongleId); + + if (deviceIndex !== -1) { + state.devices[deviceIndex] = { + ...state.devices[deviceIndex], + rpc: { + ...state.devices[deviceIndex].rpc, + ...action.fields, + }, + }; + } + + if (state.device.dongle_id === action.dongleId) { + state.device = { + ...state.device, + rpc: { + ...state.device.rpc, + ...action.fields, + }, + }; + } + break; case Types.ACTION_PRIME_NAV: state = { ...state, From 8154a3366834ed44008fd5401b8b52a263726775 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Fri, 29 May 2026 11:27:37 -0700 Subject: [PATCH 2/8] Add body UI icon improvements --- src/components/BodyTeleop/index.jsx | 2 + src/components/DeviceInfo/index.jsx | 77 +++++++++++++++++++--------- src/icons/body.png | Bin 0 -> 454 bytes 3 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 src/icons/body.png diff --git a/src/components/BodyTeleop/index.jsx b/src/components/BodyTeleop/index.jsx index f9dbdb7d8..ac5ee6f3c 100644 --- a/src/components/BodyTeleop/index.jsx +++ b/src/components/BodyTeleop/index.jsx @@ -162,6 +162,7 @@ const BodyTeleop = ({ dongleId, device, onClose }) => { <> @@ -202,6 +203,7 @@ const BodyTeleop = ({ dongleId, device, onClose }) => { {connected && ( diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx index 9dda12c56..790938e00 100644 --- a/src/components/DeviceInfo/index.jsx +++ b/src/components/DeviceInfo/index.jsx @@ -17,6 +17,7 @@ import ResizeHandler from '../ResizeHandler'; import VisibilityHandler from '../VisibilityHandler'; import TimeSelect from '../TimeSelect' import CommacareBadge from '../CommacareBadge'; +import BodyIcon from '../../icons/body.png'; import BodyTeleop from '../BodyTeleop'; const styles = (theme) => ({ @@ -46,6 +47,20 @@ const styles = (theme) => ({ deviceTitle: { display: 'flex', alignItems: 'center', + gap: '16px', + }, + bodyIconWrapper: { + width: 35, + height: 28, + borderRadius: '5px', + backgroundColor: Colors.grey500, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + bodyIcon: { + width: 20, + objectFit: 'contain', }, button: { backgroundColor: Colors.white, @@ -99,7 +114,7 @@ const styles = (theme) => ({ carBattery: { padding: '5px 16px', borderRadius: 15, - margin: '0 10px', + margin: '0 0px', textAlign: 'center', '& p': { fontSize: 14, @@ -374,6 +389,7 @@ class DeviceInfo extends Component { const { classes, device } = this.props; const { snapshot, deviceStats, windowWidth, bodyTeleopOpen } = this.state; const commacare = device?.commacare; + const isCommaBody = device?.rpc?.not_car; const containerPadding = windowWidth > 520 ? 36 : 16; const largeSnapshotPadding = windowWidth > 1440 ? '12px 0' : 0; @@ -387,7 +403,14 @@ class DeviceInfo extends Component { ? (
- {commacare && this.props.dispatch(primeNav(true))} />} + {commacare && this.props.dispatch(primeNav(true))} />} + {isCommaBody && ( + +
+ comma body +
+
+ )} {deviceNamePretty(device)}
{ this.renderStats() }
@@ -398,7 +421,14 @@ class DeviceInfo extends Component { <>
- {commacare && this.props.dispatch(primeNav(true))} />} + {commacare && this.props.dispatch(primeNav(true))} />} + {isCommaBody && ( + +
+ comma body +
+
+ )} {deviceNamePretty(device)}
@@ -526,25 +556,7 @@ class DeviceInfo extends Component { const bodyTeleopEnabled = isCommaBody && deviceVersionAtLeast(device, '0.11.2'); return ( - <> - {bodyTeleopEnabled && ( - - - - - - )} +
)}
- {!bodyTeleopEnabled && ( + {bodyTeleopEnabled ? ( + + + + + + ) : (
); } diff --git a/src/icons/body.png b/src/icons/body.png new file mode 100644 index 0000000000000000000000000000000000000000..529bfd721c45af5c193797dc7e30ac7d9218f38a GIT binary patch literal 454 zcmV;%0XhDOP)_Q$01#+~nzojV8C8TH8p*nrh2W*&suf8?``a2LS60>#bh@F5_D1Kb0O zZ@YkWHU^|{iH3VXF@7H?Dr^%_x~$pK0uowB+YNyA2#L`4kJ50y{7S@*0A2ZGX*kQu zpIfu{QNW@bXA)N)xu(kw5W4hi9{@h!4umfKdI4u)m+m0|paxto06-169nid916+!L w)xZl30jq&mWXK0OBHVy6K;pD@fMB}1ZYe0N Date: Fri, 29 May 2026 12:45:42 -0700 Subject: [PATCH 3/8] fix mobile device info header --- src/components/DeviceInfo/index.jsx | 107 +++++++++++++++------------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx index 790938e00..4cc93e380 100644 --- a/src/components/DeviceInfo/index.jsx +++ b/src/components/DeviceInfo/index.jsx @@ -23,7 +23,6 @@ import BodyTeleop from '../BodyTeleop'; const styles = (theme) => ({ container: { borderBottom: `1px solid ${Colors.white10}`, - paddingTop: 8, display: 'flex', flexDirection: 'column', minHeight: 64, @@ -33,10 +32,6 @@ const styles = (theme) => ({ display: 'flex', justifyContent: 'space-between', alignItems: 'center', - marginBottom: 4, - [theme.breakpoints.down('xs')]: { - marginBottom: 8, - }, }, columnGap: { columnGap: theme.spacing.unit * 4, @@ -109,7 +104,6 @@ const styles = (theme) => ({ flexDirection: 'column', alignItems: 'center', maxWidth: 80, - padding: `0 ${theme.spacing.unit * 4}px`, }, carBattery: { padding: '5px 16px', @@ -398,51 +392,63 @@ class DeviceInfo extends Component { <> -
- { windowWidth >= 768 - ? ( -
-
- {commacare && this.props.dispatch(primeNav(true))} />} - {isCommaBody && ( - -
- comma body -
-
- )} - {deviceNamePretty(device)} -
-
{ this.renderStats() }
-
{ this.renderButtons() }
-
- ) - : ( - <> -
-
- {commacare && this.props.dispatch(primeNav(true))} />} - {isCommaBody && ( - -
- comma body -
-
- )} - {deviceNamePretty(device)} +
+
+
+ {commacare && this.props.dispatch(primeNav(true))} />} + {isCommaBody && ( + +
+ comma body
-
-
- { this.renderButtons() } -
- { deviceStats.result - && ( -
- { this.renderStats() } -
+ )} - - ) } + {deviceNamePretty(device)} +
+
{ this.renderStats() }
+
{ this.renderButtons() }
+
+ {/* // { windowWidth >= 768 */} + {/* // ? ( + //
+ //
+ // {commacare && this.props.dispatch(primeNav(true))} />} + // {isCommaBody && ( + // + //
+ // comma body + //
+ //
+ // )} + // {deviceNamePretty(device)} + //
+ //
{ this.renderStats() }
+ //
{ this.renderButtons() }
+ //
+ // ) + // : ( + // <> + //
+ //
+ // {commacare && this.props.dispatch(primeNav(true))} />} + // {isCommaBody && ( + // + //
+ // comma body + //
+ //
+ // )} + // {deviceNamePretty(device)} + //
+ //
+ //
{ this.renderButtons() }
+ // { deviceStats.result && ( + //
+ // { this.renderStats() } + //
+ // )} + // + // ) } */}
{ snapshot.result && ( @@ -556,7 +562,7 @@ class DeviceInfo extends Component { const bodyTeleopEnabled = isCommaBody && deviceVersionAtLeast(device, '0.11.2'); return ( -
+
From 28f1c98da99cd0db0c631f318afddaa2ab358052 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Fri, 29 May 2026 12:52:54 -0700 Subject: [PATCH 4/8] restore spacing on big screen --- src/components/DeviceInfo/index.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx index 4cc93e380..69c70fc66 100644 --- a/src/components/DeviceInfo/index.jsx +++ b/src/components/DeviceInfo/index.jsx @@ -104,6 +104,7 @@ const styles = (theme) => ({ flexDirection: 'column', alignItems: 'center', maxWidth: 80, + padding: `0 ${theme.spacing.unit * 4}px`, }, carBattery: { padding: '5px 16px', @@ -405,7 +406,7 @@ class DeviceInfo extends Component { )} {deviceNamePretty(device)}
-
{ this.renderStats() }
+
{ this.renderStats() }
{ this.renderButtons() }
{/* // { windowWidth >= 768 */} From 22f93f0fbe8e0ddce0d9548151ab4d1d7d79075f Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Fri, 29 May 2026 12:53:25 -0700 Subject: [PATCH 5/8] remove old code --- src/components/DeviceInfo/index.jsx | 41 ----------------------------- 1 file changed, 41 deletions(-) diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx index 69c70fc66..2a05eb8e8 100644 --- a/src/components/DeviceInfo/index.jsx +++ b/src/components/DeviceInfo/index.jsx @@ -409,47 +409,6 @@ class DeviceInfo extends Component {
{ this.renderStats() }
{ this.renderButtons() }
- {/* // { windowWidth >= 768 */} - {/* // ? ( - //
- //
- // {commacare && this.props.dispatch(primeNav(true))} />} - // {isCommaBody && ( - // - //
- // comma body - //
- //
- // )} - // {deviceNamePretty(device)} - //
- //
{ this.renderStats() }
- //
{ this.renderButtons() }
- //
- // ) - // : ( - // <> - //
- //
- // {commacare && this.props.dispatch(primeNav(true))} />} - // {isCommaBody && ( - // - //
- // comma body - //
- //
- // )} - // {deviceNamePretty(device)} - //
- //
- //
{ this.renderButtons() }
- // { deviceStats.result && ( - //
- // { this.renderStats() } - //
- // )} - // - // ) } */}
{ snapshot.result && ( From 0814471412316c141a06bedb6afe2e427c73e17a Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Fri, 29 May 2026 13:08:23 -0700 Subject: [PATCH 6/8] clean diff --- src/components/BodyTeleop/index.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/BodyTeleop/index.jsx b/src/components/BodyTeleop/index.jsx index ac5ee6f3c..f9dbdb7d8 100644 --- a/src/components/BodyTeleop/index.jsx +++ b/src/components/BodyTeleop/index.jsx @@ -162,7 +162,6 @@ const BodyTeleop = ({ dongleId, device, onClose }) => { <> @@ -203,7 +202,6 @@ const BodyTeleop = ({ dongleId, device, onClose }) => { {connected && ( From 80b3f4cda4f249e73cce7985573592cc7b9787e5 Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Fri, 29 May 2026 13:13:40 -0700 Subject: [PATCH 7/8] fix lint --- src/components/DeviceInfo/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx index 2a05eb8e8..6ba2201a9 100644 --- a/src/components/DeviceInfo/index.jsx +++ b/src/components/DeviceInfo/index.jsx @@ -382,7 +382,7 @@ class DeviceInfo extends Component { render() { const { classes, device } = this.props; - const { snapshot, deviceStats, windowWidth, bodyTeleopOpen } = this.state; + const { snapshot, windowWidth, bodyTeleopOpen } = this.state; const commacare = device?.commacare; const isCommaBody = device?.rpc?.not_car; From 4b34b6ffba4f016360c048d60a008f6593c573ff Mon Sep 17 00:00:00 2001 From: stefpi <19478336+stefpi@users.noreply.github.com> Date: Fri, 29 May 2026 13:40:25 -0700 Subject: [PATCH 8/8] reduce padding on big screen --- src/components/DeviceInfo/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx index 6ba2201a9..99496e66b 100644 --- a/src/components/DeviceInfo/index.jsx +++ b/src/components/DeviceInfo/index.jsx @@ -394,7 +394,7 @@ class DeviceInfo extends Component {
-
+
{commacare && this.props.dispatch(primeNav(true))} />} {isCommaBody && (