Repository navigation
Add RH-style visual marshal with SD RSSI history capture - #15
Merged
Merged
Conversation
Record filtered RSSI during races to an SD sidecar with a small RAM staging buffer, expose samples via /api/marshal/rssi, and add a race history Marshal UI to review peaks and edit lap times.
Add graph zoom/pan, marker drag placement, Ctrl multi-select delete, theme-aligned styling, Gate/Lap labels, collapsible help, and refresh race history timeline/analytics after lap edits.
Review follow-ups on the Visual Marshal branch.
/api/marshal/rssi built the whole sample array as one contiguous String
reserved at samples*4+96, on top of two full copies of the sample buffer,
and request->send() copied it again. At the 45000-sample cap that document
is 160,899 bytes, so a long race could not be served. It is now emitted
incrementally through beginChunkedResponse by MarshalRssiStream, and
loadSamples trims the header off the buffer it already read instead of
allocating a second one.
MarshalRssiStream returns 0 only once the document is complete, because
ESPAsyncWebServer treats a zero-length chunk as end of body. A host-side
harness checks its output byte-for-byte against the previous encoding for
chunk sizes from 1 to 1 MB, including every ragged size up to 300.
RSSI staging was flushed to SD from inside handleLapTimerUpdate, so every
512 samples the lap-detection loop could block for up to 200 ms on the SPI
mutex plus the card write. The recorder now owns three staging buffers, a
write queue and a low-priority writer task; addSample only ever posts a
full buffer and takes a fresh one with zero wait. If the writer falls
behind the sample is dropped and counted rather than stalling detection.
Dropped samples are no longer added to sampleCount, which would have
skewed the graph's time axis, and finalizeHeader now trusts the on-disk
byte count and patches the 12 header bytes in place instead of reading and
rewriting the whole sidecar at the end of every race.
Also: strip a UTF-8 BOM from racehistory.cpp; check data.status in
marshalSaveLaps, since the endpoint answers 200 with {"status":"ERROR"} on
a failed write; and fall back to English per key in i18n.t(), because the
marshal strings exist only in en.json and de/es/fr/zh-CN users would
otherwise see raw keys such as history.marshal_title.
spiffs on custom_8mb.csv was 1MB, sized back in v1.3.1 when data/ was around 650KB. data/ has since grown to 907KB on v1.7.3, and LittleFS rounds every file up to a 4096-byte block, so the AIO image now lands at roughly 95% full and any further UI work fails to build with "No more free space". Nothing was using the 2.94MB of the 8MB chip past 0x510000, and the layout was lopsided: the 4MB SuperMini already gets 1.4MB of filesystem and the 16MB XIAO S3 Plus gets 2MB, so the 8MB boards had the smallest filesystem of any target. spiffs is now 0x3E0000 (3.875MB), which takes the image to about 25% full, and the last 64KB becomes a coredump partition so panics leave a backtrace. nvs, otadata, app0 and app1 keep their original offsets and sizes, so an OTA app update still lands correctly and saved settings survive. spiffs keeps its 0x410000 offset, so the documented esptool command and the per-release FLASH_INSTRUCTIONS.txt are unchanged. Devices that OTA-update only the app keep their old 1MB table and need one full reflash before they can take a filesystem image built against this one. That needs calling out in the release notes for whichever version ships it. Verified: FPVGateAIO, ESP32S3 and FPVGateSolo all build a 4,063,232 byte image.
Viewing a race offered both Edit and Marshal, which did substantially the same
job from two different modals. Marshal is now the single editor, renamed Edit,
with the race details it was missing.
Race name, tag, total distance and notes move into a side panel beside the lap
list, so the graph keeps the width it needs. Save now writes both, posting
/races/update for the details and /races/updateLaps for the laps. Both endpoints
answer 200 with {"status":"ERROR"} on a failed write, so a shared assertSaved
helper checks the body rather than the HTTP status; the old Marshal save
reported success regardless.
The Marshal button used to be conditional on the race having an RSSI sidecar,
so races recorded before this feature, or with no SD card in, only ever had the
Edit button. With one editor those races would have lost lap editing entirely,
so they now get typed entry in seconds where the graph would be, with Add Lap
and per-row delete. The two lap lists are alternatives and never appear
together.
Typed edits are held in a copy on marshalState rather than in raceHistoryData,
so closing without saving no longer leaves the cached race mutated, and the
inputs are read back before any re-render so adding a lap cannot discard edits
to the other rows. Both were bugs in the edit modal this replaces.
Removes the standalone #editRaceModal and 171 lines of superseded JavaScript.
tests/race-editor.test.js brace-matches segmentsToAbsPasses and
absPassesToSegments out of data/script.js and runs the real implementations,
covering the lossless round trip, out-of-order markers sorting back to positive
laps, typed-entry validation and the history gate. jest 37/37.
Race History showed recent races below much older ones, and with enough races on the card the newest could vanish from the web UI entirely. Nothing in the path sorted by date. Storage::listDir returns names in raw openNextFile() order, RaceHistory::loadRaces appended them as they came, toJsonString then emitted only the first MAX_RACES entries of that order, and the front end assigned the array through untouched. Race files are named DDMMYY-HHMMSS.json, so even a clean alphabetical listing sorts by day of month before month and year: a race from 2 August (020826) sorts ahead of one from 11 September (110926), which is exactly why the newest races appeared last. saveRace inserts at the front, so ordering was right during a session and only broke after a reload. That is why this surfaced now: loadRaces previously ran only in setup() with LittleFS mounted, so races on the SD card were never loaded at boot. Reloading once the card mounts finally gave the missing sort something to get wrong. Sorting by timestamp descending fixes both symptoms, since the MAX_RACES cap then keeps the newest races rather than an arbitrary selection. The load message is now an unconditional Serial.printf that reports the count and says when the cap is trimming what the web UI receives, because working out how many races were actually on the card needed a reflash to answer. Verified on an AIO: GET /races returns 5 races in strict newest-first order, and the self test reports storage type SD with race history 5/50.
|
Warning Review limit reachedNext included review available in 51 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (17)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a RotorHazard-style visual marshal for reviewing and editing lap times.
Supersedes #13, which GitHub closed automatically when this branch was renamed
from
MarshalVisualGraphtofeature/VisualMarshalGraphto match the otherfeature/*branches. Same work, same commits from4939d56down, plus thefollow-ups below.
Original scope
/api/marshal/rssiThe WIP concerns from #13 are resolved
Both issues raised on the original PR have been fixed and verified on hardware:
Heap on
/api/marshal/rssi. The handler built the whole sample array as onecontiguous
Stringreserved atsamples * 4 + 96, on top of two full copies ofthe sample buffer, and
request->send()copied it again. At the 45,000-samplecap that document is 160,899 bytes. It is now emitted incrementally through
beginChunkedResponse, andloadSamplestrims the header off the buffer italready read instead of allocating a second one.
SD writes in the lap-detection loop. Staging was flushed from inside
handleLapTimerUpdate, so every 512 samples the loop could block for up to200 ms on the SPI mutex plus the card write. The recorder now owns three staging
buffers, a write queue and a low-priority writer task;
addSampleonly posts afull buffer and takes a fresh one with zero wait. Race end no longer reads and
rewrites the whole sidecar either — it patches the 12 header bytes in place.
Verified on an AIO with a 12-minute race, roughly 36,000 samples.
Also in this branch
One race editor. Viewing a race offered both Edit and Marshal, which did
substantially the same job. Marshal is now the single editor, renamed Edit, with
the race name, tag, distance and notes it was missing in a side panel. Races with
no RSSI sidecar get typed lap entry where the graph would be, so they remain
editable.
8 MB partition table.
spiffswas 1 MB, sized in v1.3.1 whendata/wasaround 650 KB. At v1.7.3 it is 907 KB and the filesystem image no longer builds.
It is now 3.875 MB, using flash that was previously unallocated.
nvs,otadata,app0andapp1keep their offsets and sizes, so OTA app updates still landcorrectly. This affects all seven 8 MB targets and
mainneeds it independentlyof this PR.
Race ordering. Nothing in the load path sorted by date, and race files are
named
DDMMYY-HHMMSS, so a race from 2 August sorted ahead of one from11 September and the newest races appeared last. Now sorted newest-first.
Release note required
Devices that OTA-update only the app keep their old 1 MB partition table and
cannot take a 3.875 MB filesystem image. Those users need one full reflash. This
must be called out in the release notes for whichever version ships it.