Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c931df6054 | ||
|
|
11b31e1cdd | ||
|
|
c16b5230a6 | ||
|
|
f5362b1cb3 |
No files matched your search
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the Mesh America Configurator catalog JSON for wadamesh.
|
||||
|
||||
Mesh America (apps.meshamerica.com — the flasher Cascadia Mesh uses) merges a
|
||||
provider "catalog" JSON into its device list. Spec: third-party hosts ONE static
|
||||
JSON (this file's output) at a stable HTTPS+CORS URL, plus the firmware bins over
|
||||
HTTPS+CORS; their app reads it live. See provider-api-spec.md from TJ Downes.
|
||||
|
||||
We host:
|
||||
- the bins on firmware.wadamesh.com/releases/TOUCH/<TAG>/ (Cloudflare HTTPS +
|
||||
`Access-Control-Allow-Origin: *`, immutable per-tag dir — see deploy/nginx/
|
||||
firmware.wadamesh.com.conf), and
|
||||
- this catalog at a stable HTTPS+CORS URL (deployed alongside the firmware).
|
||||
|
||||
Usage: python3 deploy/gen-meshamerica-catalog.py <TAG> > meshamerica-catalog.json
|
||||
(TAG defaults to the newest beta_N dir; notes come from release-notes/<TAG>.txt)
|
||||
|
||||
Re-run it each release (or wire it into scripts/release.sh) so the catalog always
|
||||
points at the newest immutable bins — the spec wants firmware URLs treated as
|
||||
immutable, so each release adds a fresh versioned URL.
|
||||
"""
|
||||
import sys, json, os, glob, re
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def newest_tag():
|
||||
tags = []
|
||||
for p in glob.glob(os.path.join(REPO, "release-notes", "beta_*.txt")):
|
||||
m = re.search(r"beta_(\d+)", os.path.basename(p))
|
||||
if m:
|
||||
tags.append(int(m.group(1)))
|
||||
return "beta_%d" % max(tags) if tags else "beta_9"
|
||||
|
||||
TAG = sys.argv[1] if len(sys.argv) > 1 else newest_tag()
|
||||
BASE = "https://firmware.wadamesh.com/releases/TOUCH/%s" % TAG
|
||||
ICON = "https://wadamesh.com/wadamesh-icon-512.png"
|
||||
|
||||
# "What's new" — one user-facing line per non-blank, non-# line of the notes file.
|
||||
notes = ""
|
||||
nf = os.path.join(REPO, "release-notes", "%s.txt" % TAG)
|
||||
if os.path.exists(nf):
|
||||
notes = "\n".join(l.strip() for l in open(nf, encoding="utf-8")
|
||||
if l.strip() and not l.strip().startswith("#"))
|
||||
|
||||
# Both touch boards are ESP32-S3, single touch-UI ("gui") role. ota_0 sits at
|
||||
# 0x10000 on both (partitions_t*_touch.csv), so flash-update@0x10000 is correct.
|
||||
BOARDS = [
|
||||
{"name": "wadamesh — Heltec V4 TFT", "slug": "heltec-v4-tft"},
|
||||
{"name": "wadamesh — LilyGo T-Deck", "slug": "tdeck"},
|
||||
]
|
||||
|
||||
def device(b):
|
||||
merged, appbin = "wadamesh-%s-merged.bin" % b["slug"], "wadamesh-%s.bin" % b["slug"]
|
||||
return {
|
||||
"maker": "wadamesh",
|
||||
"class": "wadamesh",
|
||||
"name": b["name"],
|
||||
"type": "esp32",
|
||||
"tooltip": "<img class='device' src='%s'>" % ICON,
|
||||
"firmware": [{
|
||||
"role": "gui",
|
||||
"title": "Touch UI",
|
||||
"version": {TAG: {
|
||||
"notes": notes,
|
||||
"files": [
|
||||
{"type": "flash-wipe", "name": merged,
|
||||
"url": "%s/%s" % (BASE, merged),
|
||||
"title": "Full install (bootloader + firmware)"},
|
||||
{"type": "flash-update", "name": appbin,
|
||||
"url": "%s/%s" % (BASE, appbin),
|
||||
"title": "Update (app only)"},
|
||||
],
|
||||
}},
|
||||
}],
|
||||
}
|
||||
|
||||
catalog = {
|
||||
"maker": {"wadamesh": {"name": "wadamesh"}},
|
||||
"device": [device(b) for b in BOARDS],
|
||||
}
|
||||
print(json.dumps(catalog, indent=2, ensure_ascii=False))
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"maker": {
|
||||
"wadamesh": {
|
||||
"name": "wadamesh"
|
||||
}
|
||||
},
|
||||
"device": [
|
||||
{
|
||||
"maker": "wadamesh",
|
||||
"class": "wadamesh",
|
||||
"name": "wadamesh — Heltec V4 TFT",
|
||||
"type": "esp32",
|
||||
"tooltip": "<img class='device' src='https://wadamesh.com/wadamesh-icon-512.png'>",
|
||||
"firmware": [
|
||||
{
|
||||
"role": "gui",
|
||||
"title": "Touch UI",
|
||||
"version": {
|
||||
"beta_10": {
|
||||
"notes": "",
|
||||
"files": [
|
||||
{
|
||||
"type": "flash-wipe",
|
||||
"name": "wadamesh-heltec-v4-tft-merged.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_10/wadamesh-heltec-v4-tft-merged.bin",
|
||||
"title": "Full install (bootloader + firmware)"
|
||||
},
|
||||
{
|
||||
"type": "flash-update",
|
||||
"name": "wadamesh-heltec-v4-tft.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_10/wadamesh-heltec-v4-tft.bin",
|
||||
"title": "Update (app only)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"maker": "wadamesh",
|
||||
"class": "wadamesh",
|
||||
"name": "wadamesh — LilyGo T-Deck",
|
||||
"type": "esp32",
|
||||
"tooltip": "<img class='device' src='https://wadamesh.com/wadamesh-icon-512.png'>",
|
||||
"firmware": [
|
||||
{
|
||||
"role": "gui",
|
||||
"title": "Touch UI",
|
||||
"version": {
|
||||
"beta_10": {
|
||||
"notes": "",
|
||||
"files": [
|
||||
{
|
||||
"type": "flash-wipe",
|
||||
"name": "wadamesh-tdeck-merged.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_10/wadamesh-tdeck-merged.bin",
|
||||
"title": "Full install (bootloader + firmware)"
|
||||
},
|
||||
{
|
||||
"type": "flash-update",
|
||||
"name": "wadamesh-tdeck.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_10/wadamesh-tdeck.bin",
|
||||
"title": "Update (app only)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# beta_10 — what's new (one user-facing note per non-blank, non-# line)
|
||||
|
||||
# --- T-Deck input ---
|
||||
New trackball mode — switch the T-Deck trackball between a mouse cursor (the default) and D-pad navigation (roll to move the selection, click to choose), under Settings → Keyboard. Handy if you prefer button-style navigation.
|
||||
|
||||
# --- Map ---
|
||||
microSD map tiles now fill in as you explore: with "Use microSD for tiles" on, the device also downloads any missing tiles over Wi-Fi and saves them straight onto the SD card, so your offline library grows instead of showing blank areas past its edge (thanks 1nerdherder, #20)
|
||||
|
||||
# --- Power ---
|
||||
Experimental battery saver (idle power-save): throttles the CPU when the device is parked — screen off, on battery, no app connected and the mesh quiet — to trim idle power use, without ever missing a received packet. Off by default; enable it under Settings → System → Battery. (Dan Vybiral / @Vybo, #19)
|
||||
|
||||
# --- Settings ---
|
||||
New "Battery" section under Settings → System — opens the battery & power-history screen (the same one you get by tapping the battery icon up top) and is where the experimental battery saver now lives (it moved out of the Lock settings).
|
||||
@@ -54,6 +54,28 @@ echo "listing: $(python3 -c 'import json,sys;print(", ".join(x["name"] for x in
|
||||
# frozen one. Notes come from release-notes/<tag>.txt (one per line; optional).
|
||||
python3 "$ROOT/scripts/build/gen-flasher-meta.py" "$TAG" "$LATEST" "$ROOT/release-notes/$TAG.txt"
|
||||
|
||||
# 3c. Mesh America Configurator provider catalog (apps.meshamerica.com — the web
|
||||
# flasher Cascadia Mesh uses). It is served LIVE from raw.githubusercontent
|
||||
# main, so each release must regenerate it for $TAG and push that ONE file to
|
||||
# main; otherwise the configurator keeps offering the previous beta. It points
|
||||
# at the immutable /releases/TOUCH/$TAG/ bins published below.
|
||||
# (Generator: deploy/gen-meshamerica-catalog.py — reads release-notes/$TAG.txt.)
|
||||
CATALOG="$ROOT/deploy/meshamerica-catalog.json"
|
||||
python3 "$ROOT/deploy/gen-meshamerica-catalog.py" "$TAG" > "$CATALOG.tmp" && mv "$CATALOG.tmp" "$CATALOG"
|
||||
mkdir -p "$OUT/meshamerica" && cp "$CATALOG" "$OUT/meshamerica/catalog.json" # also mirror to the VPS (enables the branded URL once its nginx location is deployed)
|
||||
if git -C "$ROOT" diff --quiet -- "$CATALOG"; then
|
||||
echo "Mesh America catalog already current for $TAG"
|
||||
elif [ "$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null)" = "main" ]; then
|
||||
if git -C "$ROOT" commit -q -m "chore: refresh Mesh America catalog for $TAG" -- "$CATALOG" \
|
||||
&& git -C "$ROOT" push origin HEAD:main; then
|
||||
echo "Mesh America catalog refreshed + pushed to main ($TAG)"
|
||||
else
|
||||
echo "WARN: Mesh America catalog commit/push failed — push deploy/meshamerica-catalog.json to main by hand"
|
||||
fi
|
||||
else
|
||||
echo "NOTE: regenerated $CATALOG for $TAG but not on 'main' — commit + push it to main so apps.meshamerica.com updates."
|
||||
fi
|
||||
|
||||
# 4. Publish to the VPS (Cloudflare caches at the edge).
|
||||
if [ -n "$DEST" ]; then
|
||||
rsync -av "$OUT/" "$DEST:$DEST_PATH/"
|
||||
|
||||
@@ -780,6 +780,16 @@ public:
|
||||
// To check if there is pending work
|
||||
bool hasPendingWork() const;
|
||||
|
||||
// Number of companion clients currently connected on any transport.
|
||||
// Used by the idle light-sleep gate (TouchSleep) to confirm no one is
|
||||
// actively talking to us before the node parks in light sleep.
|
||||
int getProtoNumClients() const { return proto_num_clients; }
|
||||
|
||||
// True if the radio is currently mid-receive of a packet (preamble→RxDone).
|
||||
// _radio is the protected Dispatcher::Radio pointer; isReceiving() returns false
|
||||
// by default and is overridden by RadioLibWrapper with a real preamble-detect check.
|
||||
bool isRadioReceiving() const { return _radio && _radio->isReceiving(); }
|
||||
|
||||
private:
|
||||
void writeOKFrame();
|
||||
void writeErrFrame(uint8_t err_code);
|
||||
|
||||
@@ -35,7 +35,7 @@ static bool s_begun = false;
|
||||
// short read (→ treat as absent → defaults); `ver` lets later builds add fields.
|
||||
static const char* KEY_CFG = "cfg";
|
||||
static const uint16_t TOUCH_CFG_MAGIC = 0x5743; // 'WC' (WadaCfg)
|
||||
static const uint8_t TOUCH_CFG_VER = 7; // v2 sig_probe/poll; v3 tz_zone; v4 hide_node_name; v5 map_night/map_zoom; v6 map text/marker visibility; v7 app_grid_large
|
||||
static const uint8_t TOUCH_CFG_VER = 10; // v2 sig_probe/poll; v3 tz_zone; v4 hide_node_name; v5 map_night/map_zoom; v6 map text/marker visibility; v7 app_grid_large; v8 ui_scale; v9 tb_keypad; v10 sleep_idle
|
||||
|
||||
// Defaults (kept identical to the historical per-key defaults).
|
||||
static const uint16_t DEFAULT_SCREEN_TIMEOUT_S = 20;
|
||||
@@ -80,6 +80,9 @@ struct __attribute__((packed)) TouchCfg {
|
||||
uint8_t map_show_tilexyz; // show the zoom + tile z/x/y line on the map (bool) — v6
|
||||
uint8_t map_show_contacts;// show contact markers on the map (bool) — v6
|
||||
uint8_t app_grid_large; // app drawer: large grid (one fewer column, bigger icons) — v7
|
||||
uint8_t ui_scale; // UI resolution scale: 0=100% 1=150% 2=200% (Tanmatsu, applied at boot) — v8
|
||||
uint8_t tb_keypad; // T-Deck trackball: 0=mouse cursor (default), 1=D-pad focus navigation — v9
|
||||
uint8_t sleep_idle; // idle light-sleep feature on/off (bool) — v10 (trailing so existing blobs default it OFF)
|
||||
};
|
||||
|
||||
static TouchCfg s_cfg;
|
||||
@@ -136,6 +139,9 @@ static void cfgSetDefaults(TouchCfg& c) {
|
||||
c.map_show_tilexyz = 1;
|
||||
c.map_show_contacts = 1;
|
||||
c.app_grid_large = 0; // default: compact app grid (T-Deck 4 cols / V4 3 cols)
|
||||
c.ui_scale = 0; // default: 100% UI scale (native resolution)
|
||||
c.tb_keypad = 0; // default: trackball = mouse cursor
|
||||
c.sleep_idle = 0; // default: idle light-sleep OFF
|
||||
}
|
||||
|
||||
// Persist the whole blob using the same end()/begin(RW)/put/end()/begin(RO)
|
||||
@@ -725,6 +731,15 @@ bool touchPrefsSetMapZoom(uint8_t z) {
|
||||
s_cfg.map_zoom = z;
|
||||
return cfgFlush();
|
||||
}
|
||||
bool touchPrefsGetSleepIdle() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.sleep_idle != 0;
|
||||
}
|
||||
bool touchPrefsSetSleepIdle(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.sleep_idle = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
bool touchPrefsGetMapShowCoords() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
@@ -763,6 +778,26 @@ bool touchPrefsSetAppGridLarge(bool on) {
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
uint8_t touchPrefsGetUiScale() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.ui_scale > 2 ? 0 : s_cfg.ui_scale; // 0=100% 1=150% 2=200%
|
||||
}
|
||||
bool touchPrefsSetUiScale(uint8_t scale) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.ui_scale = scale > 2 ? 0 : scale;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
bool touchPrefsGetTbKeypad() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.tb_keypad != 0;
|
||||
}
|
||||
bool touchPrefsSetTbKeypad(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.tb_keypad = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
// Store ALL device data (identity, prefs, contacts, channels) on the SD card
|
||||
// under /meshcomod instead of internal SPIFFS. Read at boot (main.cpp) BEFORE
|
||||
// the data loads, so changing it needs a reboot. Key "use_sd" in the "touch"
|
||||
|
||||
@@ -103,6 +103,21 @@ bool touchPrefsSetMapShowContacts(bool on);
|
||||
* Default false = the compact grid (T-Deck 4 cols / Heltec V4 3 cols). */
|
||||
bool touchPrefsGetAppGridLarge();
|
||||
bool touchPrefsSetAppGridLarge(bool on);
|
||||
/** Idle light-sleep feature: when ON, the device enters light sleep while the
|
||||
* screen is off and all activity gates pass. Default OFF. */
|
||||
bool touchPrefsGetSleepIdle();
|
||||
bool touchPrefsSetSleepIdle(bool on);
|
||||
|
||||
/* UI resolution scale (Tanmatsu): 0=100% (native 800x480), 1=150%, 2=200%. Applied at boot —
|
||||
* LVGL renders at a lower resolution and the flush upscales to the panel. Reboot to apply. */
|
||||
uint8_t touchPrefsGetUiScale();
|
||||
bool touchPrefsSetUiScale(uint8_t scale);
|
||||
|
||||
/* T-Deck trackball interaction mode: false (default) = the trackball drives a soft
|
||||
* mouse cursor; true = D-pad focus navigation (↑↓←→ move focus, ball click = enter),
|
||||
* like the Tanmatsu. Applied live (no reboot). */
|
||||
bool touchPrefsGetTbKeypad();
|
||||
bool touchPrefsSetTbKeypad(bool on);
|
||||
|
||||
/** Hide the device/profile name in the status bar and move the clock to the
|
||||
* left where the name used to be. Default false (name shown). */
|
||||
|
||||
@@ -177,6 +177,11 @@ bool wifiConfigConsumeApplyRequest() {
|
||||
}
|
||||
|
||||
void wifiConfigApply() {
|
||||
#if defined(HAS_TANMATSU)
|
||||
printf("[WIFI] apply radio_en=%d hasRuntime=%d mode=%d status=%d\n",
|
||||
(int)wifiConfigGetRadioEnabled(), (int)wifiConfigHasRuntime(),
|
||||
(int)WiFi.getMode(), (int)WiFi.status());
|
||||
#endif
|
||||
if (!wifiConfigGetRadioEnabled()) {
|
||||
WiFi.disconnect(true);
|
||||
delay(50);
|
||||
@@ -188,9 +193,13 @@ void wifiConfigApply() {
|
||||
char pwd[WIFI_CONFIG_PWD_MAX];
|
||||
wifiConfigGetSsid(ssid, sizeof(ssid));
|
||||
wifiConfigGetPwd(pwd, sizeof(pwd));
|
||||
WiFi.mode(WIFI_STA); // esp_wifi_remote needs an explicit STA mode before begin()
|
||||
WiFi.disconnect();
|
||||
delay(100);
|
||||
WiFi.begin(ssid, pwd[0] ? pwd : nullptr);
|
||||
#if defined(HAS_TANMATSU)
|
||||
printf("[WIFI] begin ssid='%s' pwlen=%d\n", ssid, (int)strlen(pwd));
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "helpers/esp32/SdNvsPrefs.h" // route prefs to file storage (SD/SPIFFS), off NVS
|
||||
// (quoted: use wadamesh's src/ copy, not the lib's stale one)
|
||||
#include "wadamesh_mark_rgb.h" // anti-aliased mesh-mark (RGB565) for the pre-LVGL boot screen
|
||||
#include "ui-touch/TouchSleep.h" // idle light-sleep controller (loopEnd called at end of loop())
|
||||
#endif
|
||||
|
||||
// Believe it or not, this std C function is busted on some platforms!
|
||||
@@ -672,4 +673,11 @@ void loop() {
|
||||
#if defined(ESP32_PLATFORM)
|
||||
board.pollHttpOtaReboot();
|
||||
#endif
|
||||
|
||||
#if defined(HAS_TOUCH_UI)
|
||||
// Idle light-sleep gate: evaluated every loop tick. g_enabled is false by
|
||||
// default (Task 1 is inert); Task 2 sets it from the NVS pref and wires
|
||||
// the real predicates so the gate can actually pass and arm light sleep.
|
||||
touchSleep::loopEnd(millis());
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "TouchSleep.h"
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
namespace touchSleep {
|
||||
namespace {
|
||||
// When the gate passes (device parked: screen off, no companion client, WiFi+BLE
|
||||
// off, on battery, mesh idle) we THROTTLE the main loop with vTaskDelay instead of
|
||||
// entering esp_light_sleep.
|
||||
//
|
||||
// Why not real light sleep: manual esp_light_sleep_start() from the Arduino loop
|
||||
// task trips the Interrupt Watchdog in this build (dual-core, no PM framework,
|
||||
// INT_WDT on both cores @ 300 ms, always-on companion/LVGL/USB tasks) — reliably,
|
||||
// regardless of duration or wake-source handling. vTaskDelay is a standard scheduler
|
||||
// yield: the FreeRTOS idle task halts the CPU (WFI) between 1 ms ticks, trimming the
|
||||
// idle busy-spin draw, with zero watchdog/hang risk. The radio stays in RX; an
|
||||
// incoming packet is serviced on the next loop iteration (<= THROTTLE_MS late).
|
||||
constexpr uint32_t THROTTLE_MS = 50;
|
||||
|
||||
uint64_t g_acc_idle_us = 0; // cumulative time spent parked/throttled
|
||||
|
||||
Hooks g_hooks = {};
|
||||
TransitionCb g_transition = nullptr;
|
||||
bool g_enabled = false; // default OFF; driven from NVS via touchPrefsGetSleepIdle() at init
|
||||
bool g_asleep_regime = false;
|
||||
uint32_t g_cycle_count = 0; // throttle cycles while parked (liveness counter)
|
||||
WakeReason g_last_reason = WakeReason::None;
|
||||
|
||||
bool gatePasses() {
|
||||
if (!g_enabled) return false;
|
||||
if (!g_hooks.screenOff || !g_hooks.screenOff()) return false;
|
||||
if (!g_hooks.noClient || !g_hooks.noClient()) return false;
|
||||
if (!g_hooks.wifiOff || !g_hooks.wifiOff()) return false;
|
||||
if (!g_hooks.bleOff || !g_hooks.bleOff()) return false;
|
||||
if (!g_hooks.onBattery || !g_hooks.onBattery()) return false;
|
||||
if (!g_hooks.meshIdle || !g_hooks.meshIdle()) return false;
|
||||
return true;
|
||||
}
|
||||
void emitTransition(bool entering) {
|
||||
if (g_transition && g_hooks.epochNow) g_transition(g_hooks.epochNow(), entering);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void begin(const Hooks& hooks) { g_hooks = hooks; }
|
||||
void onTransition(TransitionCb cb) { g_transition = cb; }
|
||||
void setEnabled(bool on) { g_enabled = on; }
|
||||
bool enabled() { return g_enabled; }
|
||||
|
||||
void loopEnd(uint32_t /*now_ms*/) {
|
||||
const bool pass = gatePasses();
|
||||
if (!pass) {
|
||||
if (g_asleep_regime) { g_asleep_regime = false; emitTransition(false); } // sun — resumed activity
|
||||
return;
|
||||
}
|
||||
if (!g_asleep_regime) { g_asleep_regime = true; emitTransition(true); } // moon — parked
|
||||
|
||||
// Yield the CPU to the idle task for THROTTLE_MS (replaces esp_light_sleep_start —
|
||||
// see the note above). Standard FreeRTOS call: cannot starve a watchdog or hang.
|
||||
const uint64_t t0 = esp_timer_get_time();
|
||||
vTaskDelay(pdMS_TO_TICKS(THROTTLE_MS));
|
||||
g_acc_idle_us += (uint64_t)(esp_timer_get_time() - t0);
|
||||
g_cycle_count++;
|
||||
g_last_reason = WakeReason::Timer; // a throttle is a timed yield
|
||||
}
|
||||
|
||||
bool isSleeping() { return g_asleep_regime; } // true while parked/throttling
|
||||
uint32_t wakeCount() { return g_cycle_count; } // throttle cycles while parked
|
||||
WakeReason lastWakeReason() { return g_last_reason; }
|
||||
|
||||
uint8_t pctAsleep() {
|
||||
const uint64_t up = (uint64_t)esp_timer_get_time();
|
||||
if (up == 0) return 0;
|
||||
uint64_t pct = (g_acc_idle_us * 100ULL) / up;
|
||||
return pct > 100 ? 100 : (uint8_t)pct;
|
||||
}
|
||||
|
||||
} // namespace touchSleep
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// Idle power-save controller for the T-Deck (ESP32-S3). When the device is parked
|
||||
// (screen off, on battery, standalone) it throttles the main loop with vTaskDelay
|
||||
// so the FreeRTOS idle task can halt the CPU between ticks. (Real esp_light_sleep
|
||||
// trips the Interrupt Watchdog in this dual-core / no-PM build — see TouchSleep.cpp
|
||||
// and .notes/2026-06-18-light-sleep-rx-design.md for the full story.)
|
||||
//
|
||||
// Decoupled from UI/mesh by predicate hooks supplied by the integration layer
|
||||
// (UITask.cpp), so this module pulls in no UI/mesh/LVGL headers.
|
||||
namespace touchSleep {
|
||||
|
||||
enum class WakeReason : uint8_t { None, Timer, Packet, Touch, Button, Other };
|
||||
|
||||
struct Hooks {
|
||||
bool (*screenOff)(); // display is off
|
||||
bool (*noClient)(); // no companion client on any transport
|
||||
bool (*wifiOff)(); // WiFi fully off
|
||||
bool (*bleOff)(); // BLE fully off
|
||||
bool (*onBattery)(); // running on battery (no USB / charge source)
|
||||
bool (*meshIdle)(); // radio not mid-RX AND send queue empty
|
||||
// ms until the soonest wake-forcing deadline (advert / clock alarm),
|
||||
// or UINT32_MAX when nothing must wake us:
|
||||
uint32_t (*nextWakeForcingDueMs)(uint32_t now_ms);
|
||||
uint32_t (*epochNow)(); // wall-clock epoch seconds (for the event log)
|
||||
};
|
||||
|
||||
// regime transition: entering==true -> went to sleep (moon)
|
||||
// entering==false -> woke to activity (sun)
|
||||
using TransitionCb = void (*)(uint32_t epoch, bool entering);
|
||||
|
||||
void begin(const Hooks& hooks); // install hooks; arm static GPIO wake config once
|
||||
void onTransition(TransitionCb cb);
|
||||
void setEnabled(bool on); // feature toggle (driven by the NVS pref)
|
||||
bool enabled();
|
||||
|
||||
void loopEnd(uint32_t now_ms); // call at the very END of the main loop()
|
||||
|
||||
// instrumentation (read by the settings / diag UI)
|
||||
bool isSleeping(); // currently in the asleep regime
|
||||
uint32_t wakeCount(); // cumulative meaningful wakes
|
||||
WakeReason lastWakeReason();
|
||||
uint8_t pctAsleep(); // % wall-time asleep since boot (0..100)
|
||||
|
||||
} // namespace touchSleep
|
||||
+1484
-187
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,148 @@
|
||||
/*******************************************************************************
|
||||
* Size: 16 px
|
||||
* Bpp: 4
|
||||
* Opts: --no-compress --bpp 4 --size 16 --font /Applications/MeshCore.app/Wrapper/Runner.app/Frameworks/App.framework/flutter_assets/packages/font_awesome_flutter/lib/fonts/fa-solid-900.ttf -r 0xF185 -r 0xF186 --format lvgl --lv-font-name sleepicons_font -o src/ui-touch/sleepicons_font.c
|
||||
******************************************************************************/
|
||||
|
||||
#include "lvgl.h"
|
||||
|
||||
#ifndef SLEEPICONS_FONT
|
||||
#define SLEEPICONS_FONT 1
|
||||
#endif
|
||||
|
||||
#if SLEEPICONS_FONT
|
||||
|
||||
/*-----------------
|
||||
* BITMAPS
|
||||
*----------------*/
|
||||
|
||||
/*Store the image of the glyphs*/
|
||||
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
/* U+F185 "" */
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x8, 0xa1, 0x0, 0x1a, 0x80,
|
||||
0x0, 0x0, 0x0, 0x0, 0xc, 0xfe, 0x55, 0xef,
|
||||
0xc0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xff,
|
||||
0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x4f, 0xff,
|
||||
0xff, 0xff, 0xf4, 0x0, 0x0, 0x8, 0xcf, 0xff,
|
||||
0xd5, 0x11, 0x5d, 0xff, 0xfc, 0x80, 0xa, 0xff,
|
||||
0xfd, 0x18, 0xee, 0x81, 0xdf, 0xff, 0xa0, 0x1,
|
||||
0xef, 0xf5, 0x8f, 0xff, 0xf8, 0x5f, 0xfe, 0x10,
|
||||
0x0, 0x5f, 0xf1, 0xef, 0xff, 0xfe, 0x1f, 0xf5,
|
||||
0x0, 0x0, 0x5f, 0xf1, 0xef, 0xff, 0xfe, 0x1f,
|
||||
0xf5, 0x0, 0x1, 0xef, 0xf5, 0x8f, 0xff, 0xf8,
|
||||
0x5f, 0xfe, 0x10, 0xa, 0xff, 0xfd, 0x18, 0xee,
|
||||
0x81, 0xdf, 0xff, 0xa0, 0x8, 0xcf, 0xff, 0xd5,
|
||||
0x11, 0x5d, 0xff, 0xfc, 0x80, 0x0, 0x0, 0x4f,
|
||||
0xff, 0xff, 0xff, 0xf4, 0x0, 0x0, 0x0, 0x0,
|
||||
0xf, 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0,
|
||||
0x0, 0xc, 0xfe, 0x55, 0xef, 0xc0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x8, 0xa1, 0x0, 0x1a, 0x80, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0,
|
||||
|
||||
/* U+F186 "" */
|
||||
0x0, 0x1, 0x7c, 0xee, 0x0, 0x0, 0x0, 0x4,
|
||||
0xef, 0xfe, 0x30, 0x0, 0x0, 0x5, 0xff, 0xff,
|
||||
0x20, 0x0, 0x0, 0x1, 0xff, 0xff, 0x80, 0x0,
|
||||
0x0, 0x0, 0x7f, 0xff, 0xf2, 0x0, 0x0, 0x0,
|
||||
0xc, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0xef,
|
||||
0xff, 0xf1, 0x0, 0x0, 0x0, 0xe, 0xff, 0xff,
|
||||
0x40, 0x0, 0x0, 0x0, 0xcf, 0xff, 0xfc, 0x0,
|
||||
0x0, 0x0, 0x7, 0xff, 0xff, 0xf8, 0x0, 0x0,
|
||||
0x0, 0x1e, 0xff, 0xff, 0xfa, 0x20, 0x0, 0x0,
|
||||
0x4f, 0xff, 0xff, 0xff, 0xdc, 0xb0, 0x0, 0x4e,
|
||||
0xff, 0xff, 0xff, 0xf5, 0x0, 0x0, 0x17, 0xce,
|
||||
0xfc, 0x81, 0x0
|
||||
};
|
||||
|
||||
|
||||
/*---------------------
|
||||
* GLYPH DESCRIPTION
|
||||
*--------------------*/
|
||||
|
||||
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 256, .box_w = 18, .box_h = 18, .ofs_x = -1, .ofs_y = -3},
|
||||
{.bitmap_index = 162, .adv_w = 192, .box_w = 13, .box_h = 14, .ofs_x = 0, .ofs_y = -1}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
* CHARACTER MAPPING
|
||||
*--------------------*/
|
||||
|
||||
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 61829, .range_length = 2, .glyph_id_start = 1,
|
||||
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*--------------------
|
||||
* ALL CUSTOM DATA
|
||||
*--------------------*/
|
||||
|
||||
#if LVGL_VERSION_MAJOR == 8
|
||||
/*Store all the custom data of the font*/
|
||||
static lv_font_fmt_txt_glyph_cache_t cache;
|
||||
#endif
|
||||
|
||||
#if LVGL_VERSION_MAJOR >= 8
|
||||
static const lv_font_fmt_txt_dsc_t font_dsc = {
|
||||
#else
|
||||
static lv_font_fmt_txt_dsc_t font_dsc = {
|
||||
#endif
|
||||
.glyph_bitmap = glyph_bitmap,
|
||||
.glyph_dsc = glyph_dsc,
|
||||
.cmaps = cmaps,
|
||||
.kern_dsc = NULL,
|
||||
.kern_scale = 0,
|
||||
.cmap_num = 1,
|
||||
.bpp = 4,
|
||||
.kern_classes = 0,
|
||||
.bitmap_format = 0,
|
||||
#if LVGL_VERSION_MAJOR == 8
|
||||
.cache = &cache
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*-----------------
|
||||
* PUBLIC FONT
|
||||
*----------------*/
|
||||
|
||||
/*Initialize a public general font descriptor*/
|
||||
#if LVGL_VERSION_MAJOR >= 8
|
||||
const lv_font_t sleepicons_font = {
|
||||
#else
|
||||
lv_font_t sleepicons_font = {
|
||||
#endif
|
||||
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
|
||||
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
|
||||
.line_height = 18, /*The maximum line height required by the font*/
|
||||
.base_line = 3, /*Baseline measured from the bottom of the line*/
|
||||
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
|
||||
.subpx = LV_FONT_SUBPX_NONE,
|
||||
#endif
|
||||
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
|
||||
.underline_position = -1,
|
||||
.underline_thickness = 1,
|
||||
#endif
|
||||
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
|
||||
#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
|
||||
.fallback = NULL,
|
||||
#endif
|
||||
.user_data = NULL,
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /*#if SLEEPICONS_FONT*/
|
||||
|
||||
Reference in new issue
Block a user