diff --git a/variants/sim/SimDisplayDriver.h b/variants/sim/SimDisplayDriver.h index 938d61fd..9ba71950 100644 --- a/variants/sim/SimDisplayDriver.h +++ b/variants/sim/SimDisplayDriver.h @@ -462,6 +462,32 @@ public: void endFrame() override { } }; +#ifdef SIM_EINK_PANEL_W +// An e-ink panel (the Wio Tracker L1 E-ink's 2.13", 122x250 before rotation): +// EinkGfxDisplay draws, exactly as GxEPDDisplay does on the board, into a 1-bit +// canvas, and a frame that changed is put on the page in paper and ink. The +// host page counts refreshes from Module.__einkRefreshes. Bodies in target.cpp, +// the one TU that pulls in the font tables. +#include +#ifndef DISPLAY_ROTATION + #define DISPLAY_ROTATION 0 +#endif +class SimEinkDisplay : public EinkGfxDisplay { + bool _on = false; + static Adafruit_GFX& canvas(); +public: + static const int PANEL_W = SIM_EINK_PANEL_W, PANEL_H = SIM_EINK_PANEL_H; + SimEinkDisplay(); + bool begin(); + bool isOn() override { return _on; } + void turnOn() override { _on = true; } + void turnOff() override { _on = false; } + void clear() override; + void setDisplayRotation(uint8_t rot) override; + void endFrame() override; +}; +#endif + extern "C" EMSCRIPTEN_KEEPALIVE inline void sim_lcd_touch(int x, int y, int down) { SimLcdDisplay::Touch& t = SimLcdDisplay::touchState(); t.x = x; t.y = y; t.down = down != 0; diff --git a/variants/sim/SimSensorManager.h b/variants/sim/SimSensorManager.h index 3a453ade..ff69df23 100644 --- a/variants/sim/SimSensorManager.h +++ b/variants/sim/SimSensorManager.h @@ -1,4 +1,5 @@ #pragma once +#include #include @@ -35,11 +36,21 @@ // type turns out to matter for a demo. class SimSensorManager : public SensorManager { static float& envTemperatureRef() { static float t = 21.0f; return t; } + bool _gps = false; // the "gps" setting, as a real board's GPS switch: the UI's toggle needs it public: LocationProvider* getLocationProvider() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + int getNumSettings() const override { return 1; } + const char* getSettingName(int i) const override { return i == 0 ? "gps" : NULL; } + const char* getSettingValue(int i) const override { return i == 0 ? (_gps ? "1" : "0") : NULL; } + bool setSettingValue(const char* name, const char* value) override { + if (strcmp(name, "gps") != 0) return false; + _gps = strcmp(value, "0") != 0; + return true; + } + static void setEnvTemperature(float celsius) { envTemperatureRef() = celsius; } }; diff --git a/variants/sim/build_wasm.sh b/variants/sim/build_wasm.sh index afb00a92..182864a0 100755 --- a/variants/sim/build_wasm.sh +++ b/variants/sim/build_wasm.sh @@ -24,6 +24,8 @@ # Usage: # variants/sim/build_wasm.sh # release-ish build (-O2) # variants/sim/build_wasm.sh debug # -O0 -g, easier to debug in devtools +# SIM_UI=lvgl variants/sim/build_wasm.sh # the L2 (web/lvgl.html) +# SIM_EINK=landscape variants/sim/build_wasm.sh # the L1 E-ink, also portrait # # Requires emsdk 6.0.9 (pinned; see variants/sim/tools/emsdk/ -- installed by # this same task, see the Phase 2 report for the exact activation command). @@ -192,6 +194,28 @@ if [ "$SIM_UI" = "lvgl" ]; then LINK_EXTRA=(-sFETCH=1) # map downloads (LvglPort.h) go through emscripten_fetch fi +# SIM_EINK=landscape|portrait: ui-new on the Wio Tracker L1 E-ink's 2.13" panel +# (122x250, solo/wio-tracker-l1-eink's two builds) instead of the OLED -> +# web/index.html?build=meshcore_sim_eink_landscape (or _portrait). +SIM_EINK="${SIM_EINK:-}" +if [ -n "$SIM_EINK" ]; then + case "$SIM_EINK" in + landscape) EINK_DEFS=(-DDISPLAY_ROTATION=1 -DEINK_LARGE_FONT=1) ;; + portrait) EINK_DEFS=(-DDISPLAY_ROTATION=0) ;; + *) echo "error: SIM_EINK must be landscape or portrait" >&2; exit 1 ;; + esac + OUT_NAME="meshcore_sim_eink_$SIM_EINK" + EXPORT_NAME=MeshCoreSimEink # distinct factory: web/mesh.html?a=... loads it beside MeshCoreSim + kept=() + for d in "${DEFINES[@]}"; do + case "$d" in -DDISPLAY_CLASS=*|-DMAX_GROUP_CHANNELS=*) ;; *) kept+=("$d") ;; esac + done + # EINK_DISPLAY_MODEL is only tested for presence outside GxEPDDisplay + # (Features.h: the e-ink settings and timings), which the sim doesn't build. + DEFINES=("${kept[@]}" -DDISPLAY_CLASS=SimEinkDisplay -DSIM_EINK_PANEL_W=122 -DSIM_EINK_PANEL_H=250 + -DEINK_DISPLAY_MODEL=SimEinkPanel -DMAX_GROUP_CHANNELS=40 "${EINK_DEFS[@]}") +fi + # -funsigned-char: carried over from Phase 1 verbatim -- real ARM cores # default `char` to unsigned; em++'s target (wasm32) defaults it to signed, # same mismatch Phase 1 hit on a native x86/ARM64 host, for the same reason diff --git a/variants/sim/target.cpp b/variants/sim/target.cpp index 5c5a1d5a..89b8a73b 100644 --- a/variants/sim/target.cpp +++ b/variants/sim/target.cpp @@ -157,4 +157,82 @@ void SimDisplayDriverCanvas::print(const char* str) { // real y position across embedded '\n's internally. _cursor_x += getTextWidth(str); } + +#ifdef SIM_EINK_PANEL_W +#include + +// The panel's RAM: 1 bit a pixel, ink = 1. GFXcanvas1 rotates like GxEPD2 does. +Adafruit_GFX& SimEinkDisplay::canvas() { + static GFXcanvas1 c(PANEL_W, PANEL_H); + return c; +} + +SimEinkDisplay::SimEinkDisplay() + : EinkGfxDisplay((DISPLAY_ROTATION & 1) ? PANEL_H : PANEL_W, + (DISPLAY_ROTATION & 1) ? PANEL_W : PANEL_H, canvas(), 1, 0) { + canvas().setRotation(DISPLAY_ROTATION); +} + +// Sizes the page's canvas to the panel as it's turned, at Module.simEinkScale +// (default 3) CSS pixels a pixel. +static void simEinkFitCanvas(int w, int h) { + EM_ASM({ + var tag = (typeof Module !== 'undefined' && Module['simInstanceTag']) ? Module['simInstanceTag'] : ''; + var c = document.getElementById(tag ? ('sim-canvas-' + tag) : 'sim-canvas'); + if (!c) { console.error('[sim] e-ink canvas not found in the host page'); return; } + var k = Module.simEinkScale || 3; + c.width = $0; c.height = $1; + c.style.width = ($0 * k) + 'px'; c.style.height = ($1 * k) + 'px'; + Module.__simCtx = c.getContext('2d'); + Module.__simCtx.imageSmoothingEnabled = false; + Module.__einkRefreshes = Module.__einkRefreshes || 0; + }, w, h); +} + +bool SimEinkDisplay::begin() { + _on = true; + simEinkFitCanvas(width(), height()); + return true; +} + +void SimEinkDisplay::clear() { + canvas().fillScreen(0); + forceRedraw(); +} + +void SimEinkDisplay::setDisplayRotation(uint8_t rot) { + canvas().setRotation(rot & 3); + setDimensions(canvas().width(), canvas().height()); + updateLayout(); + simEinkFitCanvas(width(), height()); +} + +// Only a frame that differs reaches the page, as only that one costs the +// board a refresh. Read back through getPixel(), which applies the rotation. +void SimEinkDisplay::endFrame() { + if (!frameChanged()) return; + const int w = width(), h = height(); + static uint8_t px[SIM_EINK_PANEL_W * SIM_EINK_PANEL_H]; + const GFXcanvas1& c = (const GFXcanvas1&)canvas(); + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) px[y * w + x] = c.getPixel(x, y); + EM_ASM({ + if (!Module.__simCtx) return; + var w = $1; + var h = $2; + var img = Module.__simCtx.createImageData(w, h); + var d = img.data; + for (var i = 0; i < w * h; i++) { + var ink = HEAPU8[$0 + i]; + var o = i * 4; + d[o] = ink ? 0x1a : 0xdc; + d[o + 1] = ink ? 0x1a : 0xdc; + d[o + 2] = ink ? 0x1a : 0xd2; + d[o + 3] = 255; + } + Module.__simCtx.putImageData(img, 0, 0); + Module.__einkRefreshes++; + }, px, w, h); +} +#endif // SIM_EINK_PANEL_W #endif // __EMSCRIPTEN__ diff --git a/variants/sim/web/index.html b/variants/sim/web/index.html index 6dc315b6..94685161 100644 --- a/variants/sim/web/index.html +++ b/variants/sim/web/index.html @@ -158,7 +158,13 @@
- + + + +