mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-14 15:16:40 +00:00
fix(sim): text-width/render bugs, splash version, wasm-fetch error, battery lag
Code-review pass over the buzzer/sim commits turned up several real bugs,
plus two issues found afterward from manual browser testing:
Rendering (SimDisplayDriverCanvas, variants/sim/SimDisplayDriver.h + target.cpp):
- getTextWidth() measured UTF-8 BYTES (strlen()*6), not codepoints. Since
b067e95b stopped stripping accents, any accented string now measures
double its real width -- mis-centred titles, premature ellipsis/marquee,
badges pushed off-screen. Now uses the real MiscFixedRenderer measurement
(miscFixedTextWidth()), same as SH1106Display/SSD1306Display.
- Added the matching getCodepointWidth() override (O(1) single-glyph
advance), same pattern as SSD1306Display.
- isSingleFont() was left at the base class's `false`, though this backend
only ever renders MiscFixed -- UITask.cpp's status-bar indicator height
keys off this (`lh-2` vs `lh`), so the sim drew it 2px taller than a real
board.
- print() blitted the full 128x64 canvas on every call (dozens per frame,
60fps) -- now tracks a dirty bounding box and only clears/blits the
region actually touched.
Web Audio (buzzer bridge, index.html + mesh.html):
- No AudioContext.resume() -- a context created (or later suspended) in the
'suspended' state (Safari/Firefox, or any browser backgrounding the tab)
stayed silent forever. Now resumed on every gesture.
- linearRampToValueAtTime with no anchoring setValueAtTime interpolates
from the LAST scheduled event, not "now" -- so the anti-click ramps could
effectively snap instead of fading. Fixed with cancelScheduledValues +
setValueAtTime(current) before each ramp.
- mesh.html: a gesture only armed the clicked instance's audio. Click A,
send A->B, and B (the one actually meant to beep on receipt) stayed
silent. Now any gesture arms both A and B.
- RTTTL rests (freq=0, still "playing") now explicitly hold pitch and drop
gain instead of it happening to work by coincidence.
Misc: sim_test_get_num_contacts() was missing the g_sim_ready gate every
other sim_test_* hook has, so it could return a bogus negative count before
setup() finishes seeding num_contacts.
Splash screen missing "Solo <version>" bar: variants/sim never defined
FIRMWARE_SOLO_BUILD (every real Solo board does), so SplashScreen silently
skipped that whole line -- the sim looked like a plain non-Solo companion
build. Added -D FIRMWARE_SOLO_BUILD=1 to platformio.ini and build_wasm.sh.
Verified on a real canvas screenshot: "MESHCORE 1.17.1 / 19 Aug 2026 /
Solo v1.27".
Wasm-fetch error message: "failed to start: RuntimeError: Aborted(both
async and sync fetching of the wasm failed)" is Emscripten's own opaque
message for the single most common real cause -- the page opened via
file://...index.html instead of served over http(s) (fetch() on a local
file is blocked by CORS in both Chrome and Safari, confirmed by reproducing
the exact same error/stack via file://). Both harnesses now detect
location.protocol === 'file:' and show an actionable message with the
one-line fix instead of the raw stack trace.
Battery-set latency: SimMainBoard's battery value is an exact, instantaneous
JS-set integer (see sim_battery_set_mv()), but UITask's battery-check code
polls it every 8s and runs it through an EMA (alpha=0.2) meant to smooth a
REAL board's noisy ADC -- so a value typed into the demo UI could take tens
of seconds to visibly settle. SIM_PLATFORM now checks every 250ms and skips
the EMA (nothing to smooth), since the reading is already clean. Measured
on real canvas pixels: indicator update now lands within one screen-refresh
cycle instead of up to 8s+.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -402,6 +402,10 @@ extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_send_msg_to_first_contact(const cha
|
||||
// the JS ether-tick loop poll "has advert propagation finished yet" without
|
||||
// guessing a fixed timeout.
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_get_num_contacts() {
|
||||
// Same g_sim_ready gate as every other hook here: before setup() runs,
|
||||
// BaseChatMesh::num_contacts hasn't been seeded to MAX_ANON_CONTACTS yet,
|
||||
// so getNumContacts() would report a negative count.
|
||||
if (!g_sim_ready) return 0;
|
||||
return the_mesh.getNumContacts();
|
||||
}
|
||||
|
||||
|
||||
@@ -2456,7 +2456,8 @@ void UITask::loop() {
|
||||
// above, in the #if defined(SIM_PLATFORM) && defined(__EMSCRIPTEN__)
|
||||
// block) -- this stdin-poll branch was only ever meant for Phase 1's
|
||||
// native terminal target.
|
||||
// Native terminal input: stdin is put into raw/non-canonical mode by
|
||||
//
|
||||
// stdin is put into raw/non-canonical mode by
|
||||
// variants/sim/sim_main.cpp's main(), so keys arrive here one at a time
|
||||
// with no Enter-to-submit line buffering. Non-blocking select() on fd 0
|
||||
// (VMIN=0/VTIME=0 on the fd itself would also work, but select() keeps
|
||||
@@ -2752,8 +2753,20 @@ void UITask::loop() {
|
||||
if ((int32_t)(millis() - next_batt_chck) >= 0) {
|
||||
uint16_t raw = AbstractUITask::getBattMilliVolts();
|
||||
if (raw > 0) {
|
||||
#ifdef SIM_PLATFORM
|
||||
// SimMainBoard::getBattMilliVolts() returns exactly whatever value the
|
||||
// host page's JS last set (see sim_battery_set_mv() in
|
||||
// variants/sim/SimMainBoard.h) -- a clean, instantaneous number, not a
|
||||
// noisy ADC reading. Real hardware needs the EMA below to smooth a
|
||||
// voltage divider's jitter under load; applying that same filter here
|
||||
// just makes a value typed into the demo UI visibly crawl toward its
|
||||
// target over several 8s samples, which reads as the whole sim being
|
||||
// laggy for no benefit the sim actually needs.
|
||||
_batt_mv = raw;
|
||||
#else
|
||||
// EMA filter: alpha=0.2 (80% old, 20% new) — smooths ADC noise from uneven load
|
||||
_batt_mv = (_batt_mv == 0) ? raw : (uint16_t)((_batt_mv * 4u + raw) / 5u);
|
||||
#endif
|
||||
}
|
||||
uint16_t low_mv = _node_prefs ? _node_prefs->low_batt_mv : 0;
|
||||
// Don't shut down while on external power (charging) — avoids a shutdown loop.
|
||||
@@ -2775,7 +2788,16 @@ void UITask::loop() {
|
||||
}
|
||||
shutdown();
|
||||
}
|
||||
#ifdef SIM_PLATFORM
|
||||
// A real ADC read has a real cost, worth spacing out 8s apart; the sim's
|
||||
// "read" is just returning a JS-set integer, so there's no reason to sit
|
||||
// on a stale value for up to 8s after Set was clicked. 250ms keeps this
|
||||
// a poll (not a push wired to the input's own event, which would need
|
||||
// its own plumbing) while feeling immediate.
|
||||
next_batt_chck = millis() + 250;
|
||||
#else
|
||||
next_batt_chck = millis() + 8000;
|
||||
#endif
|
||||
}
|
||||
|
||||
// GPS duty-cycle hold — tells the sensor manager whether *anything* needs
|
||||
|
||||
@@ -230,7 +230,10 @@ public:
|
||||
#ifdef PIN_BUZZER
|
||||
// Lets a host page poll the buzzer's current state every frame to drive a
|
||||
// Web Audio oscillator (see sim_buzzer_is_playing()/sim_buzzer_freq_hz()
|
||||
// in main.cpp) -- `buzzer` itself is a private member, so a free function
|
||||
// in UITask.cpp, next to sim_enqueue_key() -- they live there, not in
|
||||
// main.cpp, because the `g_sim_ui_task_for_js` pointer they dispatch
|
||||
// through is file-static to that translation unit) -- `buzzer` itself is a
|
||||
// private member, so a free function
|
||||
// outside this class needs these to reach it, same reason injectSimKey()
|
||||
// above is public.
|
||||
// Not const: genericBuzzer::isPlaying() itself isn't const-qualified (its
|
||||
|
||||
Reference in New Issue
Block a user