Skip to content

Commit bf95b60

Browse files
committed
Merge origin/main into mm/charger
Resolve schema-gen conflicts caused by the charger CSV additions (0x050/0x051) landing in parallel with main's 0x1C7 event_mode + 0x1C9 energy estimate additions (PR #293). Trivial CSV merge — both branches added new rows in different spots; took the union. All generated artifacts (proto, can.json, sensor.proto, orion.proto, Prisma, SQL, ORM, BEVO mapping) were then re-run through update_can_proto.py / sync_assets.sh / sync_schema.sh so the final generated state is a clean superset of both branches' fields. Verified with the schema-drift check locally: rerunning the chain a second time is a byte-for-byte no-op.
2 parents 22b3730 + e6e432e commit bf95b60

115 files changed

Lines changed: 12792 additions & 3946 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/schema-drift.yml

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
name: schema-drift
2+
run-name: Schema codegen drift check (${{ github.actor }})
3+
4+
# Regenerates every schema/CAN artifact from its source (can_packets.csv +
5+
# can_packets.proto) and fails if the committed artifacts are out of sync.
6+
# This makes schema / telemetry-signal updates dead-easy: change the source,
7+
# run the generators, commit — and CI guarantees nothing drifts.
8+
9+
on:
10+
pull_request:
11+
types: [opened, synchronize, reopened]
12+
paths:
13+
- 'drivers/longhorn-lib/config/**'
14+
- 'drivers/longhorn-lib/protobuf/**'
15+
- 'drivers/longhorn-lib/scripts/**'
16+
- 'telemtry/scripts/**'
17+
- 'telemtry/stack/ingest/**'
18+
- 'telemtry/stack/kafka/proto/**'
19+
- 'telemtry/analysis/database/viewer_tool/prisma/**'
20+
- 'telemtry/analysis/database/viewer_tool/protobuf/**'
21+
- 'telemtry/analysis/sql_utils/models.py'
22+
- 'BEVO/nonhermetic/assets/**'
23+
- 'BEVO/codegen.py'
24+
- 'BEVO/generated_mapping.rs'
25+
- 'BEVO/loggerd/**'
26+
- 'BEVO/config.rs'
27+
- '.github/workflows/schema-drift.yml'
28+
workflow_dispatch:
29+
30+
jobs:
31+
schema-drift:
32+
name: Schema codegen drift check
33+
runs-on: ubuntu-latest
34+
env:
35+
PYTHONDONTWRITEBYTECODE: "1"
36+
steps:
37+
- name: Checkout
38+
uses: actions/checkout@v4
39+
with:
40+
fetch-depth: 0
41+
42+
- name: Set up Python
43+
uses: actions/setup-python@v5
44+
with:
45+
python-version: "3.11"
46+
47+
- name: Install protoc + python deps
48+
run: |
49+
sudo apt-get update
50+
sudo apt-get install -y protobuf-compiler
51+
python -m pip install --upgrade pip
52+
python -m pip install "protobuf>=6,<7"
53+
54+
- name: Regenerate all schema artifacts from source
55+
env:
56+
BUILD_WORKSPACE_DIRECTORY: ${{ github.workspace }}
57+
run: |
58+
set -euxo pipefail
59+
# CSV -> proto (assigns/back-annotates stable proto field ids)
60+
python3 drivers/longhorn-lib/scripts/update_can_proto.py
61+
# CSV -> can.json + descriptor + Rust mapping
62+
bash BEVO/nonhermetic/sync_assets.sh
63+
# proto -> SQL / Prisma / SQLAlchemy models / dataclasses / sensor.proto
64+
bash telemtry/scripts/sync_schema.sh Orion
65+
66+
- name: Fail if regenerated artifacts differ from committed
67+
run: |
68+
# sensor_data.desc is a binary protoc intermediate whose bytes vary by
69+
# protoc version; exclude it and gate only on the deterministic text
70+
# artifacts (proto, can.json, mapping.rs, SQL, prisma, models, ...).
71+
if ! git diff --exit-code -- . ':(exclude)BEVO/sensor_data.desc'; then
72+
{
73+
echo "### ❌ Schema artifacts are out of sync with their sources"
74+
echo ""
75+
echo "A source (\`can_packets.csv\` / \`can_packets.proto\`) changed but the"
76+
echo "generated artifacts weren't regenerated. Run locally and commit the result:"
77+
echo ""
78+
echo '```bash'
79+
echo 'export BUILD_WORKSPACE_DIRECTORY="$PWD"'
80+
echo 'python3 drivers/longhorn-lib/scripts/update_can_proto.py'
81+
echo 'bash BEVO/nonhermetic/sync_assets.sh'
82+
echo 'bash telemtry/scripts/sync_schema.sh Orion'
83+
echo '```'
84+
} >> "$GITHUB_STEP_SUMMARY"
85+
echo "::error::Generated schema artifacts are out of sync with their sources. See job summary."
86+
exit 1
87+
fi
88+
89+
loggerd-tests:
90+
name: BEVO loggerd unit tests
91+
runs-on: ubuntu-latest
92+
steps:
93+
- name: Checkout
94+
uses: actions/checkout@v4
95+
96+
- name: Install Rust
97+
uses: dtolnay/rust-toolchain@stable
98+
99+
- name: Install protoc
100+
run: |
101+
sudo apt-get update
102+
sudo apt-get install -y protobuf-compiler
103+
104+
- name: Test loggerd (schema-driven CSV header behavior)
105+
working-directory: BEVO
106+
env:
107+
PROTOC: /usr/bin/protoc
108+
run: cargo test --bin loggerd

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,7 @@ MODULE.bazel.lock
9696
rust-project.json
9797

9898
BEVO/vpn_creds.txt.claude/
99+
100+
# Python bytecode (schema-gen scripts)
101+
__pycache__/
102+
*.pyc

BEVO/cand/main.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,23 @@ fn process_raw_data(
671671
}
672672

673673
let val = match signal.conv_type.as_str() {
674+
"uint32" => {
675+
// 0x1C9 net_energy / regen_energy use uint32 with precision
676+
// 0.001 (Wh). Previously fell through the `_ => continue` arm
677+
// so the fields were silently dropped and the website saw 0.
678+
let bytes = [
679+
payload[signal.start_byte], payload[signal.start_byte+1],
680+
payload[signal.start_byte+2], payload[signal.start_byte+3],
681+
];
682+
(u32::from_le_bytes(bytes) as f64) * signal.precision
683+
},
684+
"int32" => {
685+
let bytes = [
686+
payload[signal.start_byte], payload[signal.start_byte+1],
687+
payload[signal.start_byte+2], payload[signal.start_byte+3],
688+
];
689+
(i32::from_le_bytes(bytes) as f64) * signal.precision
690+
},
674691
"uint16" => {
675692
let bytes = [payload[signal.start_byte], payload[signal.start_byte+1]];
676693
(u16::from_le_bytes(bytes) as f64) * signal.precision

BEVO/dashd/MQTT_CONTRACT.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,61 @@ its data shown on the driver's dashboard in real time.
1717
| `lhre/dash/lapDelta` | JSON number | seconds | `-0.45` |
1818
| `lhre/dash/energyDelta` | JSON number | Wh | `3.2` |
1919
| `lhre/dash/lapsRemaining` | JSON number | laps (fractional) | `15.3` |
20+
| `lhre/dash/targetPower` | JSON number | kW | `32` |
21+
| `lhre/dash/lapTrigger` | JSON number | monotonic counter | `7` |
22+
| `lhre/dash/layout` | JSON object | lap-card layout | `{"version":1,"widgets":[…]}` |
23+
| `lhre/dash/sfGate` | JSON `[f64;4]` | `[lat1,lon1,lat2,lon2]` | `[30.39,-97.72,30.39,-97.73]` |
24+
25+
### Endurance pacing signals
26+
27+
`targetPower` and `lapTrigger` are published by the **Dash** tab on the
28+
trackside-live page (`telemtry/analysis/database/viewer_tool`) over the broker's
29+
websockets listener (port `8080`). They drive on-dash endurance pacing — the
30+
dash integrates CAN power locally and compares it to the budget, so only these
31+
two control values come off-car:
32+
33+
- **`targetPower`** — the live power budget (kW) the strategist dials in. The
34+
dash integrates real power against `targetPower * elapsed` per lap; the
35+
top energy bar runs green while under budget and red while over. It's a
36+
set-point, so the sender **republishes it ~1 Hz**. Unlike the other fields it
37+
is **held last-known on the dash across a dropout** (not nulled): instead the
38+
dash emits `targetPowerStale: true` to the frontend, which dims the bar and
39+
shows a "STALE" badge — a held budget beats a blank one for the driver.
40+
- **`lapTrigger`** — a monotonically increasing lap counter. On each **increase**
41+
the dash pops a full-screen lap card (lap time + energy used that lap) and
42+
resets the per-lap energy integrator, so pacing error can't accumulate across
43+
laps. The dash keys off the rising edge, not the absolute value. This is now a
44+
**fallback/override**: when an `sfGate` is loaded the car counts its own laps.
45+
**Reverse channel (dash → trackside).** dashd also *publishes* so the strategist
46+
can confirm the uplink and mirror the driver's screen:
47+
48+
- **`lhre/dash/state`** — JSON snapshot at ~2 Hz of what the driver sees (speed,
49+
power, soc, temperature, lap count, and the on-car pacing: `lapEnergyWh`,
50+
`budgetDeltaWh`, `lapNumber`, last-lap time/energy). The Dash tab renders this
51+
as a live mirror; if it goes silent for >3 s the panel flags the uplink down.
52+
- **`lhre/dash/ack/{targetPower,lapTrigger,sfGate}`** — retained echoes published
53+
the moment dashd ingests each control, so trackside sees "the car heard 32 kW
54+
2 s ago" rather than just "I sent it." Energy integration is authoritative
55+
on-car (survives a chromium reload), so these are the same numbers, not a
56+
re-derivation.
57+
58+
- **`sfGate`** — the start/finish line as `[lat1, lon1, lat2, lon2]`, published
59+
**retained, QoS 1** from the Dash tab's "Push S/F to car" button (sourced from
60+
the Track Builder gate). Once loaded, dashd watches `dynamics.gps` and bumps
61+
the lap counter when the car's path crosses the line — so **the per-lap reset
62+
no longer depends on the link at all.** The gate is cached to disk
63+
(`DASHD_SFGATE_PATH`, default `/tmp/BEVO_dash_sfgate.json`) so it also survives
64+
a reboot if the broker drops its retained copy.
65+
- **`layout`** — the website-authored **lap-card layout** (a `LapCardLayout` JSON
66+
object), published **retained, QoS 1** by the Dash tab's lap-screen designer
67+
(sent once, used until replaced). dashd holds it, caches it to disk
68+
(`DASHD_LAYOUT_PATH`, default `/tmp/BEVO_dash_layout.json`), forwards it to the
69+
frontend as `DashMessage.layout`, and echoes `lhre/dash/ack/layout`. The
70+
frontend validates it and renders text/value/delta/bar/gauge widgets on the lap
71+
card; a missing or malformed layout falls back to the built-in card so the
72+
driver screen never blanks. Schema/renderer: `dashd/frontend/src/dashLayout.ts`
73+
+ `LapCardRenderer.tsx` (shared with the website editor; that copy is the
74+
source of truth).
2075

2176
## Payload format
2277

@@ -56,4 +111,8 @@ mosquitto_pub -h 18.191.225.118 -t "lhre/dash/lapDelta" -m "-0.45"
56111
mosquitto_pub -h 18.191.225.118 -t "lhre/dash/lapDelta" -m "-0.45"
57112
mosquitto_pub -h 18.191.225.118 -t "lhre/dash/energyDelta" -m "3.2"
58113
mosquitto_pub -h 18.191.225.118 -t "lhre/dash/lapsRemaining" -m "15.3"
114+
115+
# Endurance pacing: set a 32 kW budget, then trigger a lap card:
116+
mosquitto_pub -h 18.191.225.118 -t "lhre/dash/targetPower" -m "32"
117+
mosquitto_pub -h 18.191.225.118 -t "lhre/dash/lapTrigger" -m "1"
59118
```

BEVO/dashd/deploy/launch_kiosk.sh

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,38 @@ done
3737
# so this can be omitted there, but launching by
3838
# hand from SSH (where DISPLAY/WAYLAND_DISPLAY
3939
# weren't set up the same way) needs it explicit.
40+
CHROMIUM_BIN=""
4041
for bin in chromium-browser chromium; do
4142
if command -v "$bin" >/dev/null 2>&1; then
42-
exec "$bin" \
43-
--kiosk \
44-
--incognito \
45-
--ozone-platform=wayland \
46-
--noerrdialogs \
47-
--disable-restore-session-state \
48-
--disable-infobars \
49-
--disable-translate \
50-
--disable-features=TranslateUI \
51-
--check-for-update-interval=604800 \
52-
--overscroll-history-navigation=0 \
53-
--password-store=basic \
54-
"$URL"
43+
CHROMIUM_BIN="$bin"
44+
break
5545
fi
5646
done
5747

58-
echo "[BEVO] No chromium binary found (tried: chromium-browser, chromium)" >&2
59-
exit 1
48+
if [[ -z "$CHROMIUM_BIN" ]]; then
49+
echo "[BEVO] No chromium binary found (tried: chromium-browser, chromium)" >&2
50+
exit 1
51+
fi
52+
53+
# Supervise the kiosk: a renderer/GPU crash or OOM kill must NOT leave a blank
54+
# dash for the rest of an endurance run. dashd and the static server already
55+
# auto-restart via systemd, but the browser did not until this loop — XDG
56+
# autostart launches it once and never again. Relaunch on exit with a short
57+
# backoff. `|| true` keeps `set -e` from aborting the loop on a nonzero exit.
58+
while true; do
59+
"$CHROMIUM_BIN" \
60+
--kiosk \
61+
--incognito \
62+
--ozone-platform=wayland \
63+
--noerrdialogs \
64+
--disable-restore-session-state \
65+
--disable-infobars \
66+
--disable-translate \
67+
--disable-features=TranslateUI \
68+
--check-for-update-interval=604800 \
69+
--overscroll-history-navigation=0 \
70+
--password-store=basic \
71+
"$URL" || true
72+
echo "[BEVO] Chromium kiosk exited; relaunching in 2s" >&2
73+
sleep 2
74+
done

0 commit comments

Comments
 (0)