Skip to content

Read the sensors and the pack on their own threads - #6

Merged
PonderForge merged 1 commit into
mainfrom
feat/threaded-sources
Sep 27, 2026
Merged

PonderForge merged 1 commit into
mainfrom
feat/threaded-sources

Conversation

@taciturnaxolotl

@taciturnaxolotl taciturnaxolotl commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Currently we have a bunch of different data sources which all have different timing requirements

source model rate blocks
Arduino sensors push 20 Hz no
JBD BMS request/response ~5 Hz yes, up to a second
strategizer compute on demand yes, ~50 ms
LVGL render loop 30–60 Hz must not exceed ~16 ms

If we move the blocking work to threads then we can keep the renderer running at a decent speed

LVGL threading

We can use LVGL's pthread layer to manage the different threads in an os agnostic way. lv_lock() must wrap lv_timer_handler() on every call since LVGL walks its object tree and redraws requiring that nothing touches any of the widget data.

BMS

this also adds the bms data layer from https://github.com/HEEV/BmsHub

The three inputs have incompatible shapes. The Arduino pushes frames at 20 Hz.
The BMS is request/response and a reply can take most of a second. The
strategizer computes for tens of milliseconds. None of that fits in a render
loop that must not stall for longer than a frame.

So each source blocks freely on its own thread and publishes into its own slot
behind its own mutex, and the UI takes a snapshot per frame. Separate locks on
purpose: a slow BMS reply must not hold up the sensor feed, and neither can
stall the display.

This needed no change to BmsHub. Its blocking read with a timeout is exactly
right on a dedicated thread and was only wrong in a render loop, so threading
deleted that work rather than moving it.

LVGL 9.3 ships a pthread OS layer, so LV_USE_OS goes from LV_OS_NONE to
LV_OS_PTHREAD and every call into LVGL is wrapped, lv_timer_handler included.
Missing that one is the classic mistake: the corruption looks random and only
appears under load. Checked by hammering a widget from a second thread at
about 5 kHz while rendering.

race_telemetry_t moves out of the dashboard header into include/telemetry.h.
A source producing telemetry should not include a UI header to learn its
shape. It grows pack voltage, current, state of charge and both energy
estimates, plus a burn window for the strategizer to fill in.

Freshness is a field rather than an inference. With threads, an idle BMS and a
dead BMS thread look identical, so each group carries the time it was last
updated and anything older than two seconds reads invalid.

telemetry_source.c is replaced by sources.c. BmsHub is optional at build time
exactly as SensorHub is, so all four combinations still build and a laptop
with no hardware still gets the demo.

The warnings job also gains -Iinclude, and slot_stopping is guarded: it is
only called by the source threads, so it was unused in the demo-only build.
@PonderForge
PonderForge merged commit 0bdcb62 into main Sep 27, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants