Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
058721fdbc | ||
|
|
ff195bbaa2 | ||
|
|
77a7420c96 | ||
|
|
86c07560e5 | ||
|
|
ec3b47bbc2 | ||
|
|
9e2eed1551 | ||
|
|
58bae913e1 | ||
|
|
6088370f5d | ||
|
|
2c099aadb6 | ||
|
|
a405513d84 | ||
|
|
399b5004e0 |
No files matched your search
@@ -0,0 +1,128 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
# Slice a version by pushing a tag:
|
||||||
|
# git tag beta_13 && git push origin beta_13
|
||||||
|
# or run it manually from the Actions tab (provide the tag as input).
|
||||||
|
#
|
||||||
|
# What it does:
|
||||||
|
# 1. builds both touch boards in the cloud, with the tag embedded
|
||||||
|
# (FIRMWARE_RELEASE_TAG) so the on-device beta number matches the tag;
|
||||||
|
# 2. creates / updates the GitHub Release for the tag with the 4 bins;
|
||||||
|
# 3. publishes the bins to wadamesh.com over SSH **iff** the VPS secrets are
|
||||||
|
# set — so the on-device update check + web flasher pick them up.
|
||||||
|
#
|
||||||
|
# Safety: nothing sensitive lives in this file. The VPS host + deploy key are
|
||||||
|
# GitHub Actions *secrets* (Settings -> Secrets and variables -> Actions):
|
||||||
|
# WADAMESH_VPS user@host of the firmware VPS
|
||||||
|
# WADAMESH_VPS_PATH e.g. /srv/wadamesh/firmware
|
||||||
|
# WADAMESH_VPS_SSH_KEY a deploy private key authorized on that host
|
||||||
|
# Until those exist, step 3 is skipped and you publish with scripts/release.sh.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['beta_*']
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Release tag to build (e.g. beta_13)'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # create the Release + upload assets (nothing more)
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
ENVS: "heltec_v4_tft_companion_radio_usb_tcp_touch LilyGo_TDeck_companion_radio_touch"
|
||||||
|
# true only when the VPS deploy key secret is configured (no secret value is exposed)
|
||||||
|
HAS_VPS: ${{ secrets.WADAMESH_VPS_SSH_KEY != '' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
- name: Cache PlatformIO
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.platformio
|
||||||
|
.pio
|
||||||
|
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
|
- name: Install PlatformIO
|
||||||
|
run: pip install --upgrade platformio
|
||||||
|
|
||||||
|
- name: Resolve tag
|
||||||
|
id: v
|
||||||
|
run: |
|
||||||
|
TAG="${{ github.event.inputs.tag }}"
|
||||||
|
[ -z "$TAG" ] && TAG="${GITHUB_REF_NAME}"
|
||||||
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Building release: $TAG"
|
||||||
|
|
||||||
|
- name: Build both touch boards (tag embedded; Wi-Fi placeholder, no baked creds)
|
||||||
|
env:
|
||||||
|
TAG: ${{ steps.v.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
unset WIFI_SSID WIFI_PWD
|
||||||
|
export PLATFORMIO_BUILD_FLAGS="-DFIRMWARE_RELEASE_TAG='\"${TAG}\"' -DFIRMWARE_VERSION='\"wadamesh ${TAG}\"'"
|
||||||
|
for e in $ENVS; do pio run -t mergebin -e "$e"; done
|
||||||
|
|
||||||
|
- name: Collect bins
|
||||||
|
env:
|
||||||
|
TAG: ${{ steps.v.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
mkdir -p dist
|
||||||
|
declare -A NAME=(
|
||||||
|
[heltec_v4_tft_companion_radio_usb_tcp_touch]=wadamesh-heltec-v4-tft
|
||||||
|
[LilyGo_TDeck_companion_radio_touch]=wadamesh-tdeck
|
||||||
|
)
|
||||||
|
for e in $ENVS; do
|
||||||
|
n=${NAME[$e]}
|
||||||
|
cp ".pio/build/$e/firmware.bin" "dist/$n.bin"
|
||||||
|
cp ".pio/build/$e/firmware-merged.bin" "dist/$n-merged.bin"
|
||||||
|
done
|
||||||
|
ls -la dist
|
||||||
|
|
||||||
|
- name: Web-flasher metadata (version.json + manifests, roll with release)
|
||||||
|
env:
|
||||||
|
TAG: ${{ steps.v.outputs.tag }}
|
||||||
|
run: python3 scripts/build/gen-flasher-meta.py "$TAG" dist "release-notes/$TAG.txt"
|
||||||
|
|
||||||
|
- name: Create / update the GitHub Release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
TAG: ${{ steps.v.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||||
|
gh release upload "$TAG" dist/* --clobber
|
||||||
|
else
|
||||||
|
gh release create "$TAG" dist/* --title "wadamesh $TAG" \
|
||||||
|
--notes "Touch firmware **$TAG** for the LilyGo T-Deck and Heltec V4 + TFT. Built by CI from \`$GITHUB_SHA\`."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Publish to wadamesh.com (VPS) — skipped until VPS secrets are set
|
||||||
|
if: env.HAS_VPS == 'true'
|
||||||
|
env:
|
||||||
|
VPS: ${{ secrets.WADAMESH_VPS }}
|
||||||
|
VPS_PATH: ${{ secrets.WADAMESH_VPS_PATH }}
|
||||||
|
SSH_KEY: ${{ secrets.WADAMESH_VPS_SSH_KEY }}
|
||||||
|
TAG: ${{ steps.v.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||||
|
printf '%s\n' "$SSH_KEY" > ~/.ssh/id_deploy && chmod 600 ~/.ssh/id_deploy
|
||||||
|
ssh-keyscan -H "${VPS#*@}" >> ~/.ssh/known_hosts 2>/dev/null || true
|
||||||
|
SSH="ssh -i ~/.ssh/id_deploy -o StrictHostKeyChecking=accept-new"
|
||||||
|
REL="$VPS_PATH/releases/TOUCH/$TAG"
|
||||||
|
$SSH "$VPS" "mkdir -p '$REL' '$VPS_PATH/latest'"
|
||||||
|
for n in wadamesh-heltec-v4-tft wadamesh-tdeck; do
|
||||||
|
scp -i ~/.ssh/id_deploy "dist/$n.bin" "dist/$n-merged.bin" "$VPS:$REL/"
|
||||||
|
scp -i ~/.ssh/id_deploy "dist/$n-merged.bin" "dist/$n.bin" "$VPS:$VPS_PATH/latest/"
|
||||||
|
done
|
||||||
|
scp -i ~/.ssh/id_deploy dist/version.json dist/manifest-tdeck.json dist/manifest-heltec-v4-tft.json "$VPS:$VPS_PATH/latest/"
|
||||||
|
$SSH "$VPS" "cd '$VPS_PATH/releases/TOUCH' && python3 -c \"import os,json; b=sorted(d for d in os.listdir('.') if d.startswith('beta_') and os.path.isdir(d)); open('index.json','w').write(json.dumps([{'name':x,'type':'dir'} for x in b], indent=2))\""
|
||||||
|
rm -f ~/.ssh/id_deploy
|
||||||
|
echo "Published $TAG to wadamesh.com"
|
||||||
@@ -25,9 +25,12 @@ MeshCore fork via PlatformIO `lib_deps`.
|
|||||||
This repo holds only the **app**: the `companion_radio` glue, the `ui-touch`
|
This repo holds only the **app**: the `companion_radio` glue, the `ui-touch`
|
||||||
LVGL UI, the two boards' glue/variants, and `platformio.ini`. The **MeshCore
|
LVGL UI, the two boards' glue/variants, and `platformio.ini`. The **MeshCore
|
||||||
core is not vendored here** — it's pulled as a library via `lib_deps` from the
|
core is not vendored here** — it's pulled as a library via `lib_deps` from the
|
||||||
fork [`ALLFATHER-BV/meshcomod-core`](https://github.com/ALLFATHER-BV/meshcomod-core) (a clean
|
[`ALLFATHER-BV/meshcomod`](https://github.com/ALLFATHER-BV/meshcomod) monorepo
|
||||||
fork of meshcore-dev/MeshCore), pinned by git tag. The build is byte-identical to
|
(the same repo as the non-touch firmware), pinned by a lean source-only `core-*`
|
||||||
the original in-tree meshcomod firmware.
|
git tag. The touch-app files this repo owns (TouchPrefsStore, WifiRuntimeStore,
|
||||||
|
the transports, …) are dropped from the lib via `-DMC_VENDORED_TOUCH_APP` so they
|
||||||
|
aren't compiled twice. The build is byte-identical to the original in-tree
|
||||||
|
meshcomod firmware.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
|
|||||||
+22
-2
@@ -245,6 +245,10 @@
|
|||||||
<button class="back" data-back>← choose a different path</button>
|
<button class="back" data-back>← choose a different path</button>
|
||||||
<div class="guide-head"><h3>Standalone install</h3></div>
|
<div class="guide-head"><h3>Standalone install</h3></div>
|
||||||
<p class="p">Plug the device in over USB and flash it right here. Works in <b>Chrome</b> or <b>Edge</b> on desktop.</p>
|
<p class="p">Plug the device in over USB and flash it right here. Works in <b>Chrome</b> or <b>Edge</b> on desktop.</p>
|
||||||
|
<div data-whatsnew hidden style="margin:18px auto 0;max-width:560px;background:rgba(21,182,166,.07);border:1px solid rgba(21,182,166,.28);border-radius:12px;padding:13px 16px;text-align:left">
|
||||||
|
<div style="font-weight:600">What's new in <span data-wn-tag style="color:var(--teal)">the latest build</span></div>
|
||||||
|
<ul data-wn-notes style="margin:7px 0 0;padding-left:18px;font-size:.9rem;color:var(--mut);line-height:1.55"></ul>
|
||||||
|
</div>
|
||||||
<div class="steps" style="margin-top:22px">
|
<div class="steps" style="margin-top:22px">
|
||||||
<div class="step"><div class="num">1</div><div><h4>Connect over USB</h4><p>Plug your T-Deck or Heltec V4 into your computer with a data cable.</p></div></div>
|
<div class="step"><div class="num">1</div><div><h4>Connect over USB</h4><p>Plug your T-Deck or Heltec V4 into your computer with a data cable.</p></div></div>
|
||||||
<div class="step"><div class="num">2</div><div><h4>Pick your board & install</h4><p>Hit <b>Install</b> below, choose the serial port, and let it flash. It does a clean full install.</p></div></div>
|
<div class="step"><div class="num">2</div><div><h4>Pick your board & install</h4><p>Hit <b>Install</b> below, choose the serial port, and let it flash. It does a clean full install.</p></div></div>
|
||||||
@@ -252,9 +256,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="boards">
|
<div class="boards">
|
||||||
<div class="board"><h4>Heltec V4 + TFT</h4><p>ESP32-S3 with CHSC6x touch.</p>
|
<div class="board"><h4>Heltec V4 + TFT</h4><p>ESP32-S3 with CHSC6x touch.</p>
|
||||||
<esp-web-install-button manifest="./manifest-heltec-v4-tft.json"></esp-web-install-button></div>
|
<esp-web-install-button manifest="https://firmware.wadamesh.com/latest/manifest-heltec-v4-tft.json"></esp-web-install-button></div>
|
||||||
<div class="board"><h4>LilyGo T-Deck / Plus</h4><p>ESP32-S3 keyboard handheld.</p>
|
<div class="board"><h4>LilyGo T-Deck / Plus</h4><p>ESP32-S3 keyboard handheld.</p>
|
||||||
<esp-web-install-button manifest="./manifest-tdeck.json"></esp-web-install-button></div>
|
<esp-web-install-button manifest="https://firmware.wadamesh.com/latest/manifest-tdeck.json"></esp-web-install-button></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="noserial">Your browser can't flash here — use <b>Chrome</b> or <b>Edge</b> on a desktop.</div>
|
<div class="noserial">Your browser can't flash here — use <b>Chrome</b> or <b>Edge</b> on a desktop.</div>
|
||||||
<p class="hint">T-Deck not detected? Hold the trackball while tapping reset to force download mode.</p>
|
<p class="hint">T-Deck not detected? Hold the trackball while tapping reset to force download mode.</p>
|
||||||
@@ -379,6 +383,22 @@ We use no tracking, analytics, or advertising cookies. Our CDN and security prov
|
|||||||
try { if (!localStorage.getItem('wm-cookie-ack')) cbar.classList.add('show'); } catch(_) {}
|
try { if (!localStorage.getItem('wm-cookie-ack')) cbar.classList.add('show'); } catch(_) {}
|
||||||
document.getElementById('cookieok').addEventListener('click', () => { cbar.classList.remove('show'); try { localStorage.setItem('wm-cookie-ack','1'); } catch(_) {} });
|
document.getElementById('cookieok').addEventListener('click', () => { cbar.classList.remove('show'); try { localStorage.setItem('wm-cookie-ack','1'); } catch(_) {} });
|
||||||
|
|
||||||
|
// Live release version + "what's new" — read from the firmware host so the
|
||||||
|
// flasher always reflects the current build (the release pipeline updates
|
||||||
|
// version.json; no site redeploy needed per release).
|
||||||
|
fetch('https://firmware.wadamesh.com/latest/version.json', {cache:'no-store'})
|
||||||
|
.then(r => r.ok ? r.json() : null)
|
||||||
|
.then(v => {
|
||||||
|
if (!v || !v.tag) return;
|
||||||
|
document.querySelectorAll('[data-wn-tag]').forEach(e => e.textContent = v.tag);
|
||||||
|
const notes = Array.isArray(v.notes) ? v.notes : [];
|
||||||
|
document.querySelectorAll('[data-wn-notes]').forEach(ul => {
|
||||||
|
ul.innerHTML = '';
|
||||||
|
notes.forEach(n => { const li = document.createElement('li'); li.textContent = n; ul.appendChild(li); });
|
||||||
|
});
|
||||||
|
if (notes.length) document.querySelectorAll('[data-whatsnew]').forEach(b => b.hidden = false);
|
||||||
|
}).catch(()=>{});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- animated mesh backdrop — Vanta NET (3D dot-and-line network) in wadamesh teal on white.
|
<!-- animated mesh backdrop — Vanta NET (3D dot-and-line network) in wadamesh teal on white.
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "wadamesh — Heltec V4 TFT",
|
|
||||||
"version": "beta_1",
|
|
||||||
"new_install_prompt_erase": true,
|
|
||||||
"builds": [
|
|
||||||
{
|
|
||||||
"chipFamily": "ESP32-S3",
|
|
||||||
"parts": [
|
|
||||||
{ "path": "https://firmware.wadamesh.com/latest/wadamesh-heltec-v4-tft-merged.bin", "offset": 0 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "wadamesh — LilyGo T-Deck",
|
|
||||||
"version": "beta_1",
|
|
||||||
"new_install_prompt_erase": true,
|
|
||||||
"builds": [
|
|
||||||
{
|
|
||||||
"chipFamily": "ESP32-S3",
|
|
||||||
"parts": [
|
|
||||||
{ "path": "https://firmware.wadamesh.com/latest/wadamesh-tdeck-merged.bin", "offset": 0 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
+7
-3
@@ -18,6 +18,7 @@ extra_scripts = pre:scripts/inject_wifi_env.py, merge-bin.py
|
|||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
-w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||||
|
-D MC_VENDORED_TOUCH_APP
|
||||||
-D LORA_FREQ=869.618
|
-D LORA_FREQ=869.618
|
||||||
-D LORA_BW=62.5
|
-D LORA_BW=62.5
|
||||||
-D LORA_SF=8
|
-D LORA_SF=8
|
||||||
@@ -162,8 +163,10 @@ build_src_filter =
|
|||||||
+<../variants/heltec_v4/*.cpp>
|
+<../variants/heltec_v4/*.cpp>
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
; MeshCore core — the upstreamable fork, pinned by tag (no vendored src/ here)
|
; MeshCore core = the meshcomod monorepo built as a lib, pinned by tag.
|
||||||
https://github.com/ALLFATHER-BV/meshcomod-core.git#v1.16.0-wada.1
|
; Touch-app files (TouchPrefsStore, WifiRuntimeStore, transports, ...) are
|
||||||
|
; vendored in this app's src/ and dropped from the lib via -DMC_VENDORED_TOUCH_APP.
|
||||||
|
https://github.com/ALLFATHER-BV/meshcomod.git#core-v1.16.2
|
||||||
SPI
|
SPI
|
||||||
Wire
|
Wire
|
||||||
jgromes/RadioLib @ ^7.6.0
|
jgromes/RadioLib @ ^7.6.0
|
||||||
@@ -210,6 +213,7 @@ extra_scripts = pre:scripts/inject_wifi_env.py, merge-bin.py
|
|||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
-w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||||
|
-D MC_VENDORED_TOUCH_APP
|
||||||
-D LORA_FREQ=869.618
|
-D LORA_FREQ=869.618
|
||||||
-D LORA_BW=62.5
|
-D LORA_BW=62.5
|
||||||
-D LORA_SF=8
|
-D LORA_SF=8
|
||||||
@@ -320,7 +324,7 @@ build_src_filter =
|
|||||||
+<../variants/lilygo_tdeck/*.cpp>
|
+<../variants/lilygo_tdeck/*.cpp>
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/ALLFATHER-BV/meshcomod-core.git#v1.16.0-wada.1
|
https://github.com/ALLFATHER-BV/meshcomod.git#core-v1.16.2
|
||||||
SPI
|
SPI
|
||||||
Wire
|
Wire
|
||||||
jgromes/RadioLib @ ^7.6.0
|
jgromes/RadioLib @ ^7.6.0
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
New WADAMESH boot logo + teal default theme
|
||||||
|
Wi-Fi scan no longer crashes when no network is connected
|
||||||
|
Wi-Fi settings apply live — no more reboot prompt
|
||||||
|
Factory reset now fully wipes the device (SD-storage fix)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
"Heard repeats" indicator works again on networks with a region set
|
||||||
|
Fixed a reboot when opening Trace route on one of your sent messages
|
||||||
|
Fixed garbled "recently heard" / contacts from malformed adverts
|
||||||
|
Lighter settings storage — helps avoid the shared-NVS "storage full" issue
|
||||||
|
Clock no longer stuck near 1970 after a reboot until Wi-Fi/GPS syncs
|
||||||
|
Setup wizard and About now show the WADAMESH name
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Generate the web-flasher metadata that must roll with every release:
|
||||||
|
# - version.json : {tag, notes[]} — the site shows this ("what's new")
|
||||||
|
# - manifest-tdeck.json : esp-web-tools manifest (its "version" drives the
|
||||||
|
# - manifest-heltec-v4-tft.json install dialog, so it must equal the tag)
|
||||||
|
#
|
||||||
|
# These live next to the rolling bins at firmware.wadamesh.com/latest/, so the
|
||||||
|
# flasher always reflects the current release without a site redeploy.
|
||||||
|
#
|
||||||
|
# Usage: gen-flasher-meta.py <tag> <outdir> [notes_file]
|
||||||
|
# notes_file: one note per non-empty line (plain text); optional.
|
||||||
|
import sys, json, os
|
||||||
|
|
||||||
|
tag = sys.argv[1]
|
||||||
|
outdir = sys.argv[2]
|
||||||
|
notes_file = sys.argv[3] if len(sys.argv) > 3 else None
|
||||||
|
|
||||||
|
notes = []
|
||||||
|
if notes_file and os.path.exists(notes_file):
|
||||||
|
notes = [ln.strip() for ln in open(notes_file) if ln.strip() and not ln.startswith("#")]
|
||||||
|
|
||||||
|
os.makedirs(outdir, exist_ok=True)
|
||||||
|
|
||||||
|
with open(os.path.join(outdir, "version.json"), "w") as f:
|
||||||
|
json.dump({"tag": tag, "notes": notes}, f, indent=2)
|
||||||
|
|
||||||
|
BOARDS = {
|
||||||
|
"manifest-tdeck.json": ("wadamesh — LilyGo T-Deck", "wadamesh-tdeck-merged.bin"),
|
||||||
|
"manifest-heltec-v4-tft.json": ("wadamesh — Heltec V4 TFT", "wadamesh-heltec-v4-tft-merged.bin"),
|
||||||
|
}
|
||||||
|
for fn, (name, binf) in BOARDS.items():
|
||||||
|
manifest = {
|
||||||
|
"name": name,
|
||||||
|
"version": tag,
|
||||||
|
"new_install_prompt_erase": True,
|
||||||
|
"builds": [{
|
||||||
|
"chipFamily": "ESP32-S3",
|
||||||
|
"parts": [{"path": f"https://firmware.wadamesh.com/latest/{binf}", "offset": 0}],
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
with open(os.path.join(outdir, fn), "w") as f:
|
||||||
|
json.dump(manifest, f, indent=2)
|
||||||
|
|
||||||
|
print(f"wrote version.json + 2 manifests for {tag} -> {outdir} (notes: {len(notes)})")
|
||||||
Executable
+44
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Cut a lean, source-only MeshCore tag on the meshcomod monorepo for wadamesh to
|
||||||
|
# pin as its core lib_dep.
|
||||||
|
#
|
||||||
|
# Why: the monorepo (ALLFATHER-BV/meshcomod) is the single source of truth for the
|
||||||
|
# MeshCore core AND the non-touch firmware, but it also carries ~1.2 GB of
|
||||||
|
# prebuilt/ release bins. PlatformIO SHALLOW-clones a git lib_dep, so pinning main
|
||||||
|
# (or a normal tag) would pull that 1.2 GB into wadamesh's lib clone every CI run.
|
||||||
|
# This script creates a tag whose TREE drops prebuilt/ + bootstrap-backup/, so the
|
||||||
|
# shallow clone is ~12 MB. main is untouched (keeps prebuilt/ for the flasher/OTA);
|
||||||
|
# the lean commit is reachable only via the tag.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/cut-core-tag.sh core-v1.16.2 [/path/to/meshcomod]
|
||||||
|
# then bump lib_deps in platformio.ini to '...meshcomod.git#core-v1.16.2' + rebuild.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TAG="${1:?usage: cut-core-tag.sh <tag> e.g. core-v1.16.2 [monorepo-path]}"
|
||||||
|
MONO="${2:-/Users/kaj/Meshcomod_Touch}"
|
||||||
|
DROP="prebuilt bootstrap-backup" # heavy, non-core dirs wadamesh never builds
|
||||||
|
|
||||||
|
cd "$MONO"
|
||||||
|
git fetch origin --quiet
|
||||||
|
WT="$(mktemp -d)/core-tag"
|
||||||
|
git worktree add --no-checkout -d "$WT" origin/main >/dev/null
|
||||||
|
cleanup() { git worktree remove "$WT" --force 2>/dev/null || true; git worktree prune; }
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
cd "$WT"
|
||||||
|
git reset -q --mixed origin/main # populate index from main (no 1.2 GB checkout)
|
||||||
|
git rm -r --cached --quiet $DROP
|
||||||
|
git commit -q -m "build(core): source-only snapshot for the wadamesh core pin ($TAG)
|
||||||
|
|
||||||
|
Drops $DROP from the tree wadamesh shallow-clones as its MeshCore lib_dep.
|
||||||
|
main keeps them for the flasher/OTA; this commit is reachable only via the tag."
|
||||||
|
|
||||||
|
# sanity: prebuilt gone, core intact
|
||||||
|
[ "$(git ls-tree -r --name-only HEAD | grep -c '^prebuilt/')" = 0 ] || { echo "!! prebuilt still present"; exit 1; }
|
||||||
|
[ "$(git ls-tree -r --name-only HEAD | grep -c '^src/')" -gt 100 ] || { echo "!! src/ missing — aborting"; exit 1; }
|
||||||
|
|
||||||
|
git tag -a "$TAG" -m "MeshCore core lib — lean source-only snapshot for wadamesh"
|
||||||
|
git push origin "$TAG"
|
||||||
|
echo "pushed lean tag $TAG -> $(git rev-parse --short HEAD)"
|
||||||
|
echo "next: set lib_deps in wadamesh/platformio.ini to '...meshcomod.git#$TAG' and rebuild."
|
||||||
@@ -49,6 +49,11 @@ print(json.dumps([{"name": b, "type": "dir"} for b in betas], indent=2))
|
|||||||
PY
|
PY
|
||||||
echo "listing: $(python3 -c 'import json,sys;print(", ".join(x["name"] for x in json.load(open(sys.argv[1]))))' "$REL/index.json")"
|
echo "listing: $(python3 -c 'import json,sys;print(", ".join(x["name"] for x in json.load(open(sys.argv[1]))))' "$REL/index.json")"
|
||||||
|
|
||||||
|
# 3b. Web-flasher metadata (version.json + esp-web-tools manifests) — must roll
|
||||||
|
# with the release so the flasher shows the current version + notes, not a
|
||||||
|
# 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"
|
||||||
|
|
||||||
# 4. Publish to the VPS (Cloudflare caches at the edge).
|
# 4. Publish to the VPS (Cloudflare caches at the edge).
|
||||||
if [ -n "$DEST" ]; then
|
if [ -n "$DEST" ]; then
|
||||||
rsync -av "$OUT/" "$DEST:$DEST_PATH/"
|
rsync -av "$OUT/" "$DEST:$DEST_PATH/"
|
||||||
|
|||||||
+14
-5
@@ -1359,8 +1359,10 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
|
|||||||
#if defined(DISPLAY_CLASS)
|
#if defined(DISPLAY_CLASS)
|
||||||
// Diagnostic: log EVERY received frame so we can prove what reaches the
|
// Diagnostic: log EVERY received frame so we can prove what reaches the
|
||||||
// radio. Header byte layout is
|
// radio. Header byte layout is
|
||||||
// [hasXportCodes:1][reserved:1][payload_type:4][route_type:2]
|
// [version:2][payload_type:4][route_type:2] (bits 7..0)
|
||||||
// so payload_type = (raw[0]>>2)&0x0F and route_type = raw[0]&0x03.
|
// so payload_type = (raw[0]>>2)&0x0F and route_type = raw[0]&0x03. The 4
|
||||||
|
// transport-code bytes follow the header iff route_type is a TRANSPORT_* one
|
||||||
|
// (there is NO "hasXportCodes" bit at 0x80 — that's the top of the version field).
|
||||||
if (_ui && len > 0) {
|
if (_ui && len > 0) {
|
||||||
const uint8_t ptype = (raw[0] >> 2) & 0x0F;
|
const uint8_t ptype = (raw[0] >> 2) & 0x0F;
|
||||||
const uint8_t route = raw[0] & 0x03;
|
const uint8_t route = raw[0] & 0x03;
|
||||||
@@ -1390,7 +1392,8 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
|
|||||||
// Header is byte 0; transport codes (if present) are 1-4; path_len is
|
// Header is byte 0; transport codes (if present) are 1-4; path_len is
|
||||||
// next; then path bytes; then payload. We just want the first byte of
|
// next; then path bytes; then payload. We just want the first byte of
|
||||||
// the payload as dest_hash for TXT/RSP/ACK style packets.
|
// the payload as dest_hash for TXT/RSP/ACK style packets.
|
||||||
const bool has_xport = (raw[0] & 0x80) != 0;
|
const uint8_t rt0 = raw[0] & 0x03;
|
||||||
|
const bool has_xport = (rt0 == ROUTE_TYPE_TRANSPORT_FLOOD || rt0 == ROUTE_TYPE_TRANSPORT_DIRECT);
|
||||||
int payload_start = 1 + (has_xport ? 4 : 0);
|
int payload_start = 1 + (has_xport ? 4 : 0);
|
||||||
if (payload_start < len) {
|
if (payload_start < len) {
|
||||||
uint8_t path_byte = raw[payload_start];
|
uint8_t path_byte = raw[payload_start];
|
||||||
@@ -1421,7 +1424,9 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
|
|||||||
// and bump the matching sent message's count. The fp ring only holds our own
|
// and bump the matching sent message's count. The fp ring only holds our own
|
||||||
// recent sends, so other nodes' traffic can't false-match.
|
// recent sends, so other nodes' traffic can't false-match.
|
||||||
if (len > 0 && isMsgFloodType((raw[0] >> 2) & 0x0F)) {
|
if (len > 0 && isMsgFloodType((raw[0] >> 2) & 0x0F)) {
|
||||||
const bool has_xp = (raw[0] & 0x80) != 0;
|
const uint8_t rt = raw[0] & 0x03; // route_type (PH_ROUTE_MASK)
|
||||||
|
const bool has_xp = (rt == ROUTE_TYPE_TRANSPORT_FLOOD || // 4 transport-code bytes follow the
|
||||||
|
rt == ROUTE_TYPE_TRANSPORT_DIRECT); // header iff route is a TRANSPORT_* one
|
||||||
const int ps = 1 + (has_xp ? 4 : 0);
|
const int ps = 1 + (has_xp ? 4 : 0);
|
||||||
if (ps < len) {
|
if (ps < len) {
|
||||||
const uint8_t pb = raw[ps];
|
const uint8_t pb = raw[ps];
|
||||||
@@ -1724,7 +1729,11 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
|
|||||||
}
|
}
|
||||||
|
|
||||||
memcpy(p->pubkey_prefix, contact.id.pub_key, sizeof(p->pubkey_prefix));
|
memcpy(p->pubkey_prefix, contact.id.pub_key, sizeof(p->pubkey_prefix));
|
||||||
strcpy(p->name, contact.name);
|
// contact.name is from an over-the-air advert and may fill its 32-byte field
|
||||||
|
// with no NUL terminator; an unbounded strcpy would then run past p->name and
|
||||||
|
// corrupt the advert_paths table (garbled "Found"/recently-heard entries).
|
||||||
|
strncpy(p->name, contact.name, sizeof(p->name) - 1);
|
||||||
|
p->name[sizeof(p->name) - 1] = '\0';
|
||||||
p->recv_timestamp = getRTCClock()->getCurrentTime();
|
p->recv_timestamp = getRTCClock()->getCurrentTime();
|
||||||
p->path_len = path_len;
|
p->path_len = path_len;
|
||||||
memcpy(p->path, path, p->path_len);
|
memcpy(p->path, path, p->path_len);
|
||||||
|
|||||||
+3
-2
@@ -480,8 +480,9 @@ public:
|
|||||||
_ui_trace_ping_tag = tag;
|
_ui_trace_ping_tag = tag;
|
||||||
mesh::Packet* pkt = createTrace(tag, 0, flags);
|
mesh::Packet* pkt = createTrace(tag, 0, flags);
|
||||||
if (!pkt) { _ui_trace_ping_tag = 0; return 0; }
|
if (!pkt) { _ui_trace_ping_tag = 0; return 0; }
|
||||||
if (c.out_path_len != OUT_PATH_UNKNOWN && c.out_path_len > 0) {
|
if (c.out_path_len != OUT_PATH_UNKNOWN && c.out_path_len > 0 &&
|
||||||
sendDirect(pkt, c.out_path, c.out_path_len); // walk the full route
|
c.out_path_len <= MAX_PATH_SIZE) { // clamp: a corrupt out_path_len (>64)
|
||||||
|
sendDirect(pkt, c.out_path, c.out_path_len); // would overrun sendDirect's payload memcpy -> reboot
|
||||||
} else {
|
} else {
|
||||||
uint8_t hash_sz = (uint8_t)(_prefs.path_hash_mode + 1);
|
uint8_t hash_sz = (uint8_t)(_prefs.path_hash_mode + 1);
|
||||||
if (hash_sz == 0 || hash_sz > 4) hash_sz = 1;
|
if (hash_sz == 0 || hash_sz > 4) hash_sz = 1;
|
||||||
|
|||||||
@@ -6,17 +6,218 @@
|
|||||||
|
|
||||||
#include "SdNvsPrefs.h" // NVS, or SD /meshcomod fallback when NVS is unusable (Launcher)
|
#include "SdNvsPrefs.h" // NVS, or SD /meshcomod fallback when NVS is unusable (Launcher)
|
||||||
|
|
||||||
|
#include <stddef.h> // offsetof
|
||||||
|
#include <string.h> // memcpy
|
||||||
|
|
||||||
static const char* TOUCH_NS = "touch";
|
static const char* TOUCH_NS = "touch";
|
||||||
static const char* KEY_SCR_TO = "scr_to_s";
|
|
||||||
static const char* KEY_DC_SHOW = "dc_show";
|
|
||||||
static const uint16_t DEFAULT_SCREEN_TIMEOUT_S = 20;
|
|
||||||
static const bool DEFAULT_DC_SHOW = true;
|
|
||||||
|
|
||||||
static SdNvsPrefs s_prefs;
|
static SdNvsPrefs s_prefs;
|
||||||
static bool s_begun = false;
|
static bool s_begun = false;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Packed scalar config blob ("cfg")
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// NVS is a tiny (~20 KB) partition shared across firmwares; every distinct key
|
||||||
|
// costs an entry and the namespace was filling up. The many per-key SCALAR
|
||||||
|
// settings (brightness, kb backlight, accent colour, language, …) are therefore
|
||||||
|
// packed into ONE versioned blob keyed "cfg". Strings (tile_srv, rgn_scope,
|
||||||
|
// lk_wall, channel-scopes, quick-replies), the byte blobs (fav / ign / rpw) and
|
||||||
|
// the Wi-Fi slots keep their own keys — and "use_sd" / "setup_ok" stay standalone
|
||||||
|
// keys too, because main.cpp reads (and for use_sd, writes) them directly via a
|
||||||
|
// raw Preferences open at boot, BEFORE the touch prefs load.
|
||||||
|
//
|
||||||
|
// On first run with the blob absent we read every legacy per-key into s_cfg, write
|
||||||
|
// "cfg" ONCE, and only after that durable write do we remove() the legacy keys to
|
||||||
|
// reclaim their entries. The write-before-remove ordering makes the migration
|
||||||
|
// crash-safe and idempotent (a power-cut before the write just re-migrates next
|
||||||
|
// boot; one after it finds "cfg" present and skips). `magic` rejects a garbage /
|
||||||
|
// 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 = 1;
|
||||||
|
|
||||||
|
// Defaults (kept identical to the historical per-key defaults).
|
||||||
|
static const uint16_t DEFAULT_SCREEN_TIMEOUT_S = 20;
|
||||||
|
static const uint8_t DEFAULT_BRIGHTNESS = 100;
|
||||||
|
static const uint8_t DEFAULT_KB_BL = 2; // auto
|
||||||
|
static const uint8_t DEFAULT_KB_LAYOUT = 0; // English
|
||||||
|
static const uint8_t DEFAULT_KB_SECONDARY = 0; // None
|
||||||
|
static const uint32_t DEFAULT_LOCK_COLOR = 0xE6F2FFu; // soft white
|
||||||
|
static const uint32_t DEFAULT_ACCENT = 0x15B6A6u; // brand teal
|
||||||
|
static const bool DEFAULT_DC_SHOW = true;
|
||||||
|
|
||||||
|
struct __attribute__((packed)) TouchCfg {
|
||||||
|
uint16_t magic; // TOUCH_CFG_MAGIC — rejects a garbage/short read
|
||||||
|
uint8_t ver; // TOUCH_CFG_VER — schema version
|
||||||
|
uint8_t bright; // "bright" 1..100 (clamped 5..100 on get/set)
|
||||||
|
uint8_t kb_bl; // "kb_bl" 0..2
|
||||||
|
uint8_t kb_layout; // "kblang"
|
||||||
|
uint8_t kb_secondary; // "kbsec"
|
||||||
|
uint8_t ui_lang; // "ui_lang"
|
||||||
|
uint8_t ui_rotation; // "uirot" 0..3
|
||||||
|
uint8_t dc_show; // "dc_show" bool
|
||||||
|
uint8_t use_miles; // "use_miles" bool
|
||||||
|
uint8_t tiles_from_sd; // "tiles_sd" bool
|
||||||
|
uint8_t clr_bubbles; // "clr_bub" bool
|
||||||
|
uint8_t kb_accent; // "kb_accent" bool
|
||||||
|
int8_t time_offs; // "time_offs" -23..23
|
||||||
|
uint16_t scr_to_s; // "scr_to_s"
|
||||||
|
uint16_t kb_enabled; // "kbenab" bitmask
|
||||||
|
uint16_t batt_full_mv; // "battfull"
|
||||||
|
uint32_t lock_color; // "lk_col" 0xRRGGBB
|
||||||
|
uint32_t accent; // "accent" 0xRRGGBB
|
||||||
|
uint32_t gps_baud; // "gps_baud" 0 = unset -> caller fallback
|
||||||
|
};
|
||||||
|
|
||||||
|
static TouchCfg s_cfg;
|
||||||
|
static bool s_cfg_loaded = false;
|
||||||
|
|
||||||
|
// Legacy per-key names — only referenced by the one-time migration below.
|
||||||
|
static const char* LK_SCR_TO = "scr_to_s";
|
||||||
|
static const char* LK_DC_SHOW = "dc_show";
|
||||||
|
static const char* LK_BRIGHTNESS = "bright";
|
||||||
|
static const char* LK_KB_BL = "kb_bl";
|
||||||
|
static const char* LK_KB_LAYOUT = "kblang";
|
||||||
|
static const char* LK_KB_SECONDARY = "kbsec";
|
||||||
|
static const char* LK_KB_ENABLED = "kbenab";
|
||||||
|
static const char* LK_LOCK_COLOR = "lk_col";
|
||||||
|
static const char* LK_CLR_BUBBLES = "clr_bub";
|
||||||
|
static const char* LK_KB_ACCENT = "kb_accent";
|
||||||
|
static const char* LK_ACCENT = "accent";
|
||||||
|
static const char* LK_TIME_OFFS = "time_offs";
|
||||||
|
static const char* LK_USE_MILES = "use_miles";
|
||||||
|
static const char* LK_TILES_FROM_SD= "tiles_sd";
|
||||||
|
static const char* LK_UI_LANG = "ui_lang";
|
||||||
|
static const char* LK_UI_ROTATION = "uirot";
|
||||||
|
static const char* LK_BATT_FULL = "battfull";
|
||||||
|
static const char* LK_GPS_BAUD = "gps_baud";
|
||||||
|
|
||||||
|
static void cfgSetDefaults(TouchCfg& c) {
|
||||||
|
c.magic = TOUCH_CFG_MAGIC;
|
||||||
|
c.ver = TOUCH_CFG_VER;
|
||||||
|
c.bright = DEFAULT_BRIGHTNESS;
|
||||||
|
c.kb_bl = DEFAULT_KB_BL;
|
||||||
|
c.kb_layout = DEFAULT_KB_LAYOUT;
|
||||||
|
c.kb_secondary = DEFAULT_KB_SECONDARY;
|
||||||
|
c.ui_lang = 0;
|
||||||
|
c.ui_rotation = 0;
|
||||||
|
c.dc_show = DEFAULT_DC_SHOW ? 1 : 0;
|
||||||
|
c.use_miles = 0;
|
||||||
|
c.tiles_from_sd = 0;
|
||||||
|
c.clr_bubbles = 1; // default ON
|
||||||
|
c.kb_accent = 1; // default ON
|
||||||
|
c.time_offs = 0;
|
||||||
|
c.scr_to_s = DEFAULT_SCREEN_TIMEOUT_S;
|
||||||
|
c.kb_enabled = 0;
|
||||||
|
c.batt_full_mv = 0;
|
||||||
|
c.lock_color = DEFAULT_LOCK_COLOR;
|
||||||
|
c.accent = DEFAULT_ACCENT;
|
||||||
|
c.gps_baud = 0; // 0 sentinel -> getter returns caller fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist the whole blob using the same end()/begin(RW)/put/end()/begin(RO)
|
||||||
|
// discipline every setter in this file uses. Returns true on a durable write.
|
||||||
|
static bool cfgFlush() {
|
||||||
|
s_prefs.end();
|
||||||
|
if (!s_prefs.begin(TOUCH_NS, false)) { s_begun = false; return false; }
|
||||||
|
bool ok = s_prefs.putBytes(KEY_CFG, &s_cfg, sizeof(s_cfg)) == sizeof(s_cfg);
|
||||||
|
s_prefs.end();
|
||||||
|
s_begun = s_prefs.begin(TOUCH_NS, true);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load "cfg" once; on absence run the one-time legacy migration. Must be called
|
||||||
|
// with the namespace already open (RO is fine for the load + legacy reads; the
|
||||||
|
// migration write reopens RW via cfgFlush). Idempotent: a no-op after the first
|
||||||
|
// successful run because "cfg" then exists.
|
||||||
|
static void cfgLoadOrMigrate() {
|
||||||
|
if (s_cfg_loaded) return;
|
||||||
|
cfgSetDefaults(s_cfg);
|
||||||
|
|
||||||
|
// isKey() does NOT emit the [E] NOT_FOUND log that getBytes() would on a miss,
|
||||||
|
// so probe first to keep the (USB-CDC) console clean on a fresh device.
|
||||||
|
if (s_prefs.isKey(KEY_CFG)) {
|
||||||
|
TouchCfg tmp;
|
||||||
|
memset(&tmp, 0, sizeof(tmp));
|
||||||
|
size_t n = s_prefs.getBytes(KEY_CFG, &tmp, sizeof(tmp));
|
||||||
|
// Need at least magic(2)+ver(1) to trust the header; reject anything shorter
|
||||||
|
// (a half-written / garbage blob) and re-derive from legacy keys / defaults.
|
||||||
|
if (n >= offsetof(TouchCfg, bright) && tmp.magic == TOUCH_CFG_MAGIC) {
|
||||||
|
// Copy whatever was stored over the defaults (a shorter, older blob leaves
|
||||||
|
// newer trailing fields at their default), then version-upgrade if needed.
|
||||||
|
memcpy(&s_cfg, &tmp, n < sizeof(s_cfg) ? n : sizeof(s_cfg));
|
||||||
|
if (s_cfg.ver < TOUCH_CFG_VER) {
|
||||||
|
s_cfg.ver = TOUCH_CFG_VER;
|
||||||
|
s_cfg.magic = TOUCH_CFG_MAGIC;
|
||||||
|
cfgFlush(); // rewrite with new fields defaulted-in
|
||||||
|
}
|
||||||
|
s_cfg_loaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Garbage / short / wrong-magic read -> fall through and re-derive from
|
||||||
|
// legacy keys (or defaults), overwriting the bad blob.
|
||||||
|
}
|
||||||
|
|
||||||
|
// No (valid) "cfg" yet: build it from the legacy per-key values, applying the
|
||||||
|
// exact same defaults the old getters used. On a fresh device every isKey()
|
||||||
|
// is false, so this just keeps the defaults set above.
|
||||||
|
if (s_prefs.isKey(LK_SCR_TO)) s_cfg.scr_to_s = s_prefs.getUShort(LK_SCR_TO, DEFAULT_SCREEN_TIMEOUT_S);
|
||||||
|
if (s_prefs.isKey(LK_BRIGHTNESS)) s_cfg.bright = s_prefs.getUChar(LK_BRIGHTNESS, DEFAULT_BRIGHTNESS);
|
||||||
|
if (s_prefs.isKey(LK_KB_BL)) s_cfg.kb_bl = s_prefs.getUChar(LK_KB_BL, DEFAULT_KB_BL);
|
||||||
|
if (s_prefs.isKey(LK_KB_LAYOUT)) s_cfg.kb_layout = s_prefs.getUChar(LK_KB_LAYOUT, DEFAULT_KB_LAYOUT);
|
||||||
|
if (s_prefs.isKey(LK_KB_SECONDARY)) s_cfg.kb_secondary = s_prefs.getUChar(LK_KB_SECONDARY, DEFAULT_KB_SECONDARY);
|
||||||
|
if (s_prefs.isKey(LK_TIME_OFFS)) s_cfg.time_offs = s_prefs.getChar(LK_TIME_OFFS, 0);
|
||||||
|
if (s_prefs.isKey(LK_LOCK_COLOR)) s_cfg.lock_color = s_prefs.getUInt(LK_LOCK_COLOR, DEFAULT_LOCK_COLOR) & 0xFFFFFFu;
|
||||||
|
if (s_prefs.isKey(LK_ACCENT)) s_cfg.accent = s_prefs.getUInt(LK_ACCENT, DEFAULT_ACCENT) & 0xFFFFFFu;
|
||||||
|
if (s_prefs.isKey(LK_CLR_BUBBLES)) s_cfg.clr_bubbles = s_prefs.getBool(LK_CLR_BUBBLES, true) ? 1 : 0;
|
||||||
|
if (s_prefs.isKey(LK_KB_ACCENT)) s_cfg.kb_accent = s_prefs.getBool(LK_KB_ACCENT, true) ? 1 : 0;
|
||||||
|
if (s_prefs.isKey(LK_DC_SHOW)) s_cfg.dc_show = s_prefs.getBool(LK_DC_SHOW, DEFAULT_DC_SHOW) ? 1 : 0;
|
||||||
|
if (s_prefs.isKey(LK_USE_MILES)) s_cfg.use_miles = s_prefs.getBool(LK_USE_MILES, false) ? 1 : 0;
|
||||||
|
if (s_prefs.isKey(LK_TILES_FROM_SD))s_cfg.tiles_from_sd= s_prefs.getBool(LK_TILES_FROM_SD, false) ? 1 : 0;
|
||||||
|
if (s_prefs.isKey(LK_UI_LANG)) s_cfg.ui_lang = s_prefs.getUChar(LK_UI_LANG, 0);
|
||||||
|
if (s_prefs.isKey(LK_UI_ROTATION)) s_cfg.ui_rotation = s_prefs.getUChar(LK_UI_ROTATION, 0);
|
||||||
|
if (s_prefs.isKey(LK_BATT_FULL)) s_cfg.batt_full_mv = s_prefs.getUShort(LK_BATT_FULL, 0);
|
||||||
|
if (s_prefs.isKey(LK_GPS_BAUD)) s_cfg.gps_baud = s_prefs.getUInt(LK_GPS_BAUD, 0);
|
||||||
|
// Enabled-layout mask: legacy used 0xFFFF as the "never written" sentinel and
|
||||||
|
// derived a one-bit mask from the secondary layout. Reproduce that here.
|
||||||
|
{
|
||||||
|
uint16_t v = s_prefs.isKey(LK_KB_ENABLED) ? s_prefs.getUShort(LK_KB_ENABLED, 0xFFFF) : 0xFFFF;
|
||||||
|
if (v == 0xFFFF) {
|
||||||
|
uint8_t sec = s_cfg.kb_secondary;
|
||||||
|
s_cfg.kb_enabled = (sec != 0 && sec < 16) ? (uint16_t)(1u << sec) : 0;
|
||||||
|
} else {
|
||||||
|
s_cfg.kb_enabled = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write "cfg" ONCE. Only after a durable write do we reclaim the legacy keys.
|
||||||
|
// If the write fails (e.g. NVS full / SD missing) we keep the legacy keys
|
||||||
|
// intact and retry the whole migration on the next boot.
|
||||||
|
if (cfgFlush()) {
|
||||||
|
s_prefs.end();
|
||||||
|
if (s_prefs.begin(TOUCH_NS, false)) {
|
||||||
|
const char* legacy[] = {
|
||||||
|
LK_SCR_TO, LK_DC_SHOW, LK_BRIGHTNESS, LK_KB_BL, LK_KB_LAYOUT,
|
||||||
|
LK_KB_SECONDARY, LK_KB_ENABLED, LK_LOCK_COLOR, LK_CLR_BUBBLES,
|
||||||
|
LK_KB_ACCENT, LK_ACCENT, LK_TIME_OFFS, LK_USE_MILES, LK_TILES_FROM_SD,
|
||||||
|
LK_UI_LANG, LK_UI_ROTATION, LK_BATT_FULL, LK_GPS_BAUD,
|
||||||
|
};
|
||||||
|
for (const char* k : legacy) {
|
||||||
|
if (s_prefs.isKey(k)) s_prefs.remove(k);
|
||||||
|
}
|
||||||
|
s_prefs.end();
|
||||||
|
}
|
||||||
|
s_begun = s_prefs.begin(TOUCH_NS, true);
|
||||||
|
}
|
||||||
|
s_cfg_loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
void touchPrefsBegin() {
|
void touchPrefsBegin() {
|
||||||
if (s_begun) return;
|
if (s_begun) {
|
||||||
|
if (!s_cfg_loaded) cfgLoadOrMigrate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
s_begun = s_prefs.begin(TOUCH_NS, true);
|
||||||
if (!s_begun) {
|
if (!s_begun) {
|
||||||
/* Namespace may not exist yet — open RW once to create it, then reopen RO. */
|
/* Namespace may not exist yet — open RW once to create it, then reopen RO. */
|
||||||
@@ -25,6 +226,7 @@ void touchPrefsBegin() {
|
|||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
s_begun = s_prefs.begin(TOUCH_NS, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (s_begun) cfgLoadOrMigrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arduino's Preferences::getString()/getBytes() emit an [E] nvs_get_* "NOT_FOUND"
|
// Arduino's Preferences::getString()/getBytes() emit an [E] nvs_get_* "NOT_FOUND"
|
||||||
@@ -38,25 +240,18 @@ static String prefsGetStr(const char* key, const String& def) {
|
|||||||
|
|
||||||
uint16_t touchPrefsGetScreenTimeoutSecs() {
|
uint16_t touchPrefsGetScreenTimeoutSecs() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getUShort(KEY_SCR_TO, DEFAULT_SCREEN_TIMEOUT_S);
|
return s_cfg.scr_to_s;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetScreenTimeoutSecs(uint16_t seconds) {
|
bool touchPrefsSetScreenTimeoutSecs(uint16_t seconds) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.scr_to_s = seconds;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUShort(KEY_SCR_TO, seconds) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_BRIGHTNESS = "bright";
|
|
||||||
static const uint8_t DEFAULT_BRIGHTNESS = 100;
|
|
||||||
|
|
||||||
uint8_t touchPrefsGetBrightness() {
|
uint8_t touchPrefsGetBrightness() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
uint8_t v = s_prefs.getUChar(KEY_BRIGHTNESS, DEFAULT_BRIGHTNESS);
|
uint8_t v = s_cfg.bright;
|
||||||
if (v < 5) v = 5;
|
if (v < 5) v = 5;
|
||||||
if (v > 100) v = 100;
|
if (v > 100) v = 100;
|
||||||
return v;
|
return v;
|
||||||
@@ -66,94 +261,57 @@ bool touchPrefsSetBrightness(uint8_t pct) {
|
|||||||
if (pct < 5) pct = 5;
|
if (pct < 5) pct = 5;
|
||||||
if (pct > 100) pct = 100;
|
if (pct > 100) pct = 100;
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.bright = pct;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUChar(KEY_BRIGHTNESS, pct) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_KB_BL = "kb_bl";
|
|
||||||
static const uint8_t DEFAULT_KB_BL = 2; // auto
|
|
||||||
|
|
||||||
uint8_t touchPrefsGetKbBacklight() {
|
uint8_t touchPrefsGetKbBacklight() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
uint8_t v = s_prefs.getUChar(KEY_KB_BL, DEFAULT_KB_BL);
|
uint8_t v = s_cfg.kb_bl;
|
||||||
return v > 2 ? DEFAULT_KB_BL : v;
|
return v > 2 ? DEFAULT_KB_BL : v;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetKbBacklight(uint8_t mode) {
|
bool touchPrefsSetKbBacklight(uint8_t mode) {
|
||||||
if (mode > 2) mode = DEFAULT_KB_BL;
|
if (mode > 2) mode = DEFAULT_KB_BL;
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.kb_bl = mode;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUChar(KEY_KB_BL, mode) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_KB_LAYOUT = "kblang";
|
|
||||||
static const uint8_t DEFAULT_KB_LAYOUT = 0; // English
|
|
||||||
|
|
||||||
uint8_t touchPrefsGetKeyboardLayout() {
|
uint8_t touchPrefsGetKeyboardLayout() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
uint8_t v = s_prefs.getUChar(KEY_KB_LAYOUT, DEFAULT_KB_LAYOUT);
|
return s_cfg.kb_layout;
|
||||||
return v;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetKeyboardLayout(uint8_t layout) {
|
bool touchPrefsSetKeyboardLayout(uint8_t layout) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.kb_layout = layout;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUChar(KEY_KB_LAYOUT, layout) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_KB_SECONDARY = "kbsec";
|
|
||||||
static const uint8_t DEFAULT_KB_SECONDARY = 0; // None
|
|
||||||
|
|
||||||
uint8_t touchPrefsGetSecondaryKeyboard() {
|
uint8_t touchPrefsGetSecondaryKeyboard() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
uint8_t v = s_prefs.getUChar(KEY_KB_SECONDARY, DEFAULT_KB_SECONDARY);
|
return s_cfg.kb_secondary;
|
||||||
return v;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetSecondaryKeyboard(uint8_t secondary) {
|
bool touchPrefsSetSecondaryKeyboard(uint8_t secondary) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.kb_secondary = secondary;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUChar(KEY_KB_SECONDARY, secondary) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_KB_ENABLED = "kbenab";
|
|
||||||
|
|
||||||
uint16_t touchPrefsGetEnabledLayouts() {
|
uint16_t touchPrefsGetEnabledLayouts() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
// 0xFFFF is never a valid mask (only 7 low bits are used), so use it as the
|
// The legacy "never written -> derive a one-bit mask from the secondary
|
||||||
// "never written" sentinel: migrate the legacy single-secondary into a mask.
|
// layout" migration ran once at cfg-migration time (see cfgLoadOrMigrate);
|
||||||
uint16_t v = s_prefs.getUShort(KEY_KB_ENABLED, 0xFFFF);
|
// the resolved mask now lives in s_cfg.kb_enabled.
|
||||||
if (v == 0xFFFF) {
|
return s_cfg.kb_enabled;
|
||||||
uint8_t sec = s_prefs.getUChar(KEY_KB_SECONDARY, 0);
|
|
||||||
return (sec != 0 && sec < 16) ? (uint16_t)(1u << sec) : 0;
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetEnabledLayouts(uint16_t mask) {
|
bool touchPrefsSetEnabledLayouts(uint16_t mask) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.kb_enabled = mask;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUShort(KEY_KB_ENABLED, mask) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_TILE_SRV = "tile_srv";
|
static const char* KEY_TILE_SRV = "tile_srv";
|
||||||
@@ -268,11 +426,9 @@ bool touchPrefsSetLockWallpaper(const char* path) {
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_TIME_OFFS = "time_offs";
|
|
||||||
|
|
||||||
int touchPrefsGetTimeOffsetHours() {
|
int touchPrefsGetTimeOffsetHours() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
int v = (int)s_prefs.getChar(KEY_TIME_OFFS, 0);
|
int v = (int)s_cfg.time_offs;
|
||||||
if (v < -23) v = -23;
|
if (v < -23) v = -23;
|
||||||
if (v > 23) v = 23;
|
if (v > 23) v = 23;
|
||||||
return v;
|
return v;
|
||||||
@@ -281,12 +437,8 @@ bool touchPrefsSetTimeOffsetHours(int hours) {
|
|||||||
if (hours < -23) hours = -23;
|
if (hours < -23) hours = -23;
|
||||||
if (hours > 23) hours = 23;
|
if (hours > 23) hours = 23;
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.time_offs = (int8_t)hours;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putChar(KEY_TIME_OFFS, (int8_t)hours) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
void touchPrefsBuildLocalTz(char* out, int out_cap) {
|
void touchPrefsBuildLocalTz(char* out, int out_cap) {
|
||||||
if (!out || out_cap <= 0) return;
|
if (!out || out_cap <= 0) return;
|
||||||
@@ -297,77 +449,52 @@ void touchPrefsBuildLocalTz(char* out, int out_cap) {
|
|||||||
snprintf(out, out_cap, "CET%dCEST,M3.5.0,M10.5.0/3", -(1 + off));
|
snprintf(out, out_cap, "CET%dCEST,M3.5.0,M10.5.0/3", -(1 + off));
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_LOCK_COLOR = "lk_col";
|
|
||||||
static const uint32_t DEFAULT_LOCK_COLOR = 0xE6F2FFu; // soft white
|
|
||||||
|
|
||||||
uint32_t touchPrefsGetLockTextColor() {
|
uint32_t touchPrefsGetLockTextColor() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getUInt(KEY_LOCK_COLOR, DEFAULT_LOCK_COLOR) & 0xFFFFFFu;
|
return s_cfg.lock_color & 0xFFFFFFu;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetLockTextColor(uint32_t rgb) {
|
bool touchPrefsSetLockTextColor(uint32_t rgb) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.lock_color = rgb & 0xFFFFFFu;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUInt(KEY_LOCK_COLOR, rgb & 0xFFFFFFu) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colourful chat bubbles: colour each bubble + sender name by a hash of the
|
// Colourful chat bubbles: colour each bubble + sender name by a hash of the
|
||||||
// sender's display name (same name -> same colour). Default ON. (getBool is
|
// sender's display name (same name -> same colour). Default ON.
|
||||||
// log_v on a miss, so no NOT_FOUND console spam.)
|
|
||||||
static const char* KEY_CLR_BUBBLES = "clr_bub";
|
|
||||||
bool touchPrefsGetColorfulBubbles() {
|
bool touchPrefsGetColorfulBubbles() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getBool(KEY_CLR_BUBBLES, true);
|
return s_cfg.clr_bubbles != 0;
|
||||||
}
|
}
|
||||||
bool touchPrefsSetColorfulBubbles(bool on) {
|
bool touchPrefsSetColorfulBubbles(bool on) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.clr_bubbles = on ? 1 : 0;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putBool(KEY_CLR_BUBBLES, on) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard accent-popup picker: when a typed Latin letter has accented variants,
|
// Keyboard accent-popup picker: when a typed Latin letter has accented variants,
|
||||||
// a tap-to-pick box appears. Default ON. (getBool is log_v on a miss, so no
|
// a tap-to-pick box appears. Default ON. (Distinct from the accent THEME colour.)
|
||||||
// NOT_FOUND console spam.) Key is distinct from KEY_ACCENT (the theme colour).
|
|
||||||
static const char* KEY_KB_ACCENT = "kb_accent";
|
|
||||||
bool touchPrefsGetAccentPopups() {
|
bool touchPrefsGetAccentPopups() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getBool(KEY_KB_ACCENT, true);
|
return s_cfg.kb_accent != 0;
|
||||||
}
|
}
|
||||||
bool touchPrefsSetAccentPopups(bool on) {
|
bool touchPrefsSetAccentPopups(bool on) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.kb_accent = on ? 1 : 0;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putBool(KEY_KB_ACCENT, on) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI accent colour (buttons, active tab, keyboard, highlights) as 0xRRGGBB.
|
// UI accent colour (buttons, active tab, keyboard, highlights) as 0xRRGGBB.
|
||||||
// Default = the WADAMESH brand teal (the logo dots). The picker clamps it dark
|
// Default = the WADAMESH brand teal (the logo dots). The picker clamps it dark
|
||||||
// enough that the off-white button text stays readable on any hue.
|
// enough that the off-white button text stays readable on any hue.
|
||||||
static const char* KEY_ACCENT = "accent";
|
|
||||||
static const uint32_t DEFAULT_ACCENT = 0x15B6A6u;
|
|
||||||
uint32_t touchPrefsGetAccentColor() {
|
uint32_t touchPrefsGetAccentColor() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getUInt(KEY_ACCENT, DEFAULT_ACCENT) & 0xFFFFFFu;
|
return s_cfg.accent & 0xFFFFFFu;
|
||||||
}
|
}
|
||||||
bool touchPrefsSetAccentColor(uint32_t rgb) {
|
bool touchPrefsSetAccentColor(uint32_t rgb) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.accent = rgb & 0xFFFFFFu;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUInt(KEY_ACCENT, rgb & 0xFFFFFFu) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick-reply macros. Stored as NVS strings keyed "qr0".."qr5".
|
// Quick-reply macros. Stored as NVS strings keyed "qr0".."qr5".
|
||||||
@@ -410,50 +537,35 @@ int touchPrefsGetQuickReply(int idx, char* out, int out_cap) {
|
|||||||
|
|
||||||
bool touchPrefsGetDutyMeterShown() {
|
bool touchPrefsGetDutyMeterShown() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getBool(KEY_DC_SHOW, DEFAULT_DC_SHOW);
|
return s_cfg.dc_show != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetDutyMeterShown(bool show) {
|
bool touchPrefsSetDutyMeterShown(bool show) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.dc_show = show ? 1 : 0;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putBool(KEY_DC_SHOW, show);
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_USE_MILES = "use_miles";
|
|
||||||
static const char* KEY_TILES_FROM_SD = "tiles_sd";
|
|
||||||
|
|
||||||
bool touchPrefsGetUseMiles() {
|
bool touchPrefsGetUseMiles() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getBool(KEY_USE_MILES, false); // default = km
|
return s_cfg.use_miles != 0; // default = km
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetUseMiles(bool use_miles) {
|
bool touchPrefsSetUseMiles(bool use_miles) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.use_miles = use_miles ? 1 : 0;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putBool(KEY_USE_MILES, use_miles);
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsGetTilesFromSd() {
|
bool touchPrefsGetTilesFromSd() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getBool(KEY_TILES_FROM_SD, false); // default = tile server
|
return s_cfg.tiles_from_sd != 0; // default = tile server
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetTilesFromSd(bool from_sd) {
|
bool touchPrefsSetTilesFromSd(bool from_sd) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.tiles_from_sd = from_sd ? 1 : 0;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putBool(KEY_TILES_FROM_SD, from_sd);
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store ALL device data (identity, prefs, contacts, channels) on the SD card
|
// Store ALL device data (identity, prefs, contacts, channels) on the SD card
|
||||||
@@ -478,56 +590,39 @@ bool touchPrefsSetUseSdStorage(bool use_sd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UI language index (UiLang enum in i18n.h; 0 = English). Read at boot to pick
|
// UI language index (UiLang enum in i18n.h; 0 = English). Read at boot to pick
|
||||||
// the active translation language. Key "ui_lang" in the "touch" namespace.
|
// the active translation language. Packed into the "cfg" blob.
|
||||||
static const char* KEY_UI_LANG = "ui_lang";
|
|
||||||
uint8_t touchPrefsGetUiLang() {
|
uint8_t touchPrefsGetUiLang() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getUChar(KEY_UI_LANG, 0); // default = English
|
return s_cfg.ui_lang; // default = English
|
||||||
}
|
}
|
||||||
bool touchPrefsSetUiLang(uint8_t lang) {
|
bool touchPrefsSetUiLang(uint8_t lang) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.ui_lang = lang;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUChar(KEY_UI_LANG, lang) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_UI_ROTATION = "uirot";
|
|
||||||
|
|
||||||
uint8_t touchPrefsGetUiRotation() {
|
uint8_t touchPrefsGetUiRotation() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
uint8_t r = s_prefs.getUChar(KEY_UI_ROTATION, 0); // default = portrait
|
uint8_t r = s_cfg.ui_rotation; // default = portrait
|
||||||
return (r <= 3) ? r : 0;
|
return (r <= 3) ? r : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetUiRotation(uint8_t rot) {
|
bool touchPrefsSetUiRotation(uint8_t rot) {
|
||||||
if (rot > 3) rot = 0;
|
if (rot > 3) rot = 0;
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.ui_rotation = rot;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUChar(KEY_UI_ROTATION, rot);
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_BATT_FULL = "battfull";
|
|
||||||
|
|
||||||
uint16_t touchPrefsGetBattFullMv() {
|
uint16_t touchPrefsGetBattFullMv() {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getUShort(KEY_BATT_FULL, 0); // 0 = not calibrated -> default 4200
|
return s_cfg.batt_full_mv; // 0 = not calibrated -> default 4200
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetBattFullMv(uint16_t mv) {
|
bool touchPrefsSetBattFullMv(uint16_t mv) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.batt_full_mv = mv;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUShort(KEY_BATT_FULL, mv) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wi-Fi profile slots ----------------------------------------------------
|
// Wi-Fi profile slots ----------------------------------------------------
|
||||||
@@ -884,21 +979,17 @@ bool touchPrefsSetSetupDone(bool done) {
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* KEY_GPS_BAUD = "gps_baud";
|
|
||||||
|
|
||||||
uint32_t touchPrefsGetGpsBaud(uint32_t fallback) {
|
uint32_t touchPrefsGetGpsBaud(uint32_t fallback) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
return s_prefs.getUInt(KEY_GPS_BAUD, fallback);
|
// 0 = never set -> caller's compile-time default. A real configured baud is
|
||||||
|
// always non-zero, so this preserves the old "absent key -> fallback" result.
|
||||||
|
return s_cfg.gps_baud != 0 ? s_cfg.gps_baud : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool touchPrefsSetGpsBaud(uint32_t baud) {
|
bool touchPrefsSetGpsBaud(uint32_t baud) {
|
||||||
if (!s_begun) touchPrefsBegin();
|
if (!s_begun) touchPrefsBegin();
|
||||||
s_prefs.end();
|
s_cfg.gps_baud = baud;
|
||||||
if (!s_prefs.begin(TOUCH_NS, false)) return false;
|
return cfgFlush();
|
||||||
bool ok = s_prefs.putUInt(KEY_GPS_BAUD, baud) > 0;
|
|
||||||
s_prefs.end();
|
|
||||||
s_begun = s_prefs.begin(TOUCH_NS, true);
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+4
-2
@@ -226,11 +226,13 @@ void setup() {
|
|||||||
uint32_t bn = prefs.getUInt("n", 0);
|
uint32_t bn = prefs.getUInt("n", 0);
|
||||||
++bn;
|
++bn;
|
||||||
prefs.putUInt("n", bn);
|
prefs.putUInt("n", bn);
|
||||||
|
size_t nvs_free = prefs.freeEntries(); // NVS partition headroom; near 0 = full (the boot-loop trigger)
|
||||||
prefs.end();
|
prefs.end();
|
||||||
meshcomod_touch_set_boot_stats(bn, static_cast<uint8_t>(esp_reset_reason()));
|
meshcomod_touch_set_boot_stats(bn, static_cast<uint8_t>(esp_reset_reason()));
|
||||||
Serial.printf("[BOOT] touch_boot_n=%lu reason=%u\n",
|
Serial.printf("[BOOT] touch_boot_n=%lu reason=%u nvs_free_entries=%u\n",
|
||||||
static_cast<unsigned long>(bn),
|
static_cast<unsigned long>(bn),
|
||||||
static_cast<unsigned>(esp_reset_reason()));
|
static_cast<unsigned>(esp_reset_reason()),
|
||||||
|
static_cast<unsigned>(nvs_free));
|
||||||
}
|
}
|
||||||
the_mesh.initTxtTxUniquenessFromRng();
|
the_mesh.initTxtTxUniquenessFromRng();
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-3
@@ -4858,7 +4858,7 @@ static void sysInfoText(char* buf, size_t cap) {
|
|||||||
"Last reset\n %s\n\n", resetReasonString(esp_reset_reason()));
|
"Last reset\n %s\n\n", resetReasonString(esp_reset_reason()));
|
||||||
#endif
|
#endif
|
||||||
p += snprintf(buf + p, cap - p,
|
p += snprintf(buf + p, cap - p,
|
||||||
"Build\n %s\n %s\n Meshcomod TOUCH\n",
|
"Build\n %s\n %s\n WADAMESH TOUCH\n",
|
||||||
FIRMWARE_VERSION, FIRMWARE_BUILD_DATE);
|
FIRMWARE_VERSION, FIRMWARE_BUILD_DATE);
|
||||||
(void)p;
|
(void)p;
|
||||||
}
|
}
|
||||||
@@ -6097,6 +6097,19 @@ static void wifiScanService() {
|
|||||||
wifiScanFillList(); // no-op if the popup was closed
|
wifiScanFillList(); // no-op if the popup was closed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Live Wi-Fi radio toggle on the Wi-Fi settings page (mirrors the control-center
|
||||||
|
// toggle). wifiConfigSetRadioEnabled persists the pref + flags an apply, so the
|
||||||
|
// main loop brings esp_wifi up / down on the spot — no Save needed, no reboot.
|
||||||
|
static void wifiRadioToggleCb(lv_event_t* e) {
|
||||||
|
if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
|
||||||
|
#if defined(ESP32) && defined(MULTI_TRANSPORT_COMPANION)
|
||||||
|
const bool on = lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED);
|
||||||
|
wifiConfigSetRadioEnabled(on);
|
||||||
|
if (g_lv.task) g_lv.task->showAlert(on ? TR("Wi-Fi on") : TR("Wi-Fi off"), 800);
|
||||||
|
refreshStatusLabels();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static void buildWifiSettings() {
|
static void buildWifiSettings() {
|
||||||
lv_obj_t* body = createSettingsModal("Wi-Fi", SettingsModalKind::Wifi);
|
lv_obj_t* body = createSettingsModal("Wi-Fi", SettingsModalKind::Wifi);
|
||||||
int y = 0;
|
int y = 0;
|
||||||
@@ -6112,6 +6125,7 @@ static void buildWifiSettings() {
|
|||||||
g_set_modal.wifi_sw = lv_switch_create(body);
|
g_set_modal.wifi_sw = lv_switch_create(body);
|
||||||
lv_obj_align(g_set_modal.wifi_sw, LV_ALIGN_TOP_RIGHT, 0, y); // toggle next to "Status"
|
lv_obj_align(g_set_modal.wifi_sw, LV_ALIGN_TOP_RIGHT, 0, y); // toggle next to "Status"
|
||||||
if (wifiConfigGetRadioEnabled()) lv_obj_add_state(g_set_modal.wifi_sw, LV_STATE_CHECKED);
|
if (wifiConfigGetRadioEnabled()) lv_obj_add_state(g_set_modal.wifi_sw, LV_STATE_CHECKED);
|
||||||
|
lv_obj_add_event_cb(g_set_modal.wifi_sw, wifiRadioToggleCb, LV_EVENT_VALUE_CHANGED, nullptr); // live on/off — no Save needed
|
||||||
y += 30;
|
y += 30;
|
||||||
|
|
||||||
g_set_modal.wifi_sta_status_l = lv_label_create(body);
|
g_set_modal.wifi_sta_status_l = lv_label_create(body);
|
||||||
@@ -9061,7 +9075,7 @@ static struct { fs::FS* fs; char path[200]; char name[64]; bool isdir; bool is_c
|
|||||||
s_fm_clip = {};
|
s_fm_clip = {};
|
||||||
|
|
||||||
static const char* k_term_banner =
|
static const char* k_term_banner =
|
||||||
"Meshcomod terminal - CLI + chat on this node.\n"
|
"WADAMESH terminal - CLI + chat on this node.\n"
|
||||||
"Type a command or tap the list icon for the picker.\n"
|
"Type a command or tap the list icon for the picker.\n"
|
||||||
"Chat: 'to <name>' joins a contact/channel, then just\n"
|
"Chat: 'to <name>' joins a contact/channel, then just\n"
|
||||||
" type to send. 'exit' leaves. list / channels.\n"
|
" type to send. 'exit' leaves. list / channels.\n"
|
||||||
@@ -18401,7 +18415,7 @@ static void setupShowStep(int step) {
|
|||||||
const lv_coord_t btn_y = sh - kSetupBtnH - 10;
|
const lv_coord_t btn_y = sh - kSetupBtnH - 10;
|
||||||
|
|
||||||
if (step == 0) {
|
if (step == 0) {
|
||||||
setupHeader("Welcome to Meshcomod", nullptr, nullptr);
|
setupHeader("Welcome to WADAMESH", nullptr, nullptr);
|
||||||
lv_obj_t* m = lv_label_create(s_setup_root);
|
lv_obj_t* m = lv_label_create(s_setup_root);
|
||||||
lv_label_set_text(m,
|
lv_label_set_text(m,
|
||||||
"Let's set up your device.\n\n"
|
"Let's set up your device.\n\n"
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ static const I18nEntry kI18n[] = {
|
|||||||
{ "Sound off", { 0, "Geluid uit","Ton aus","Son désactivé","Sonido apagado","Suono spento","Звук выкл","Звук вимк","Звук изкл","Звук искљ","Ήχος όχι" } },
|
{ "Sound off", { 0, "Geluid uit","Ton aus","Son désactivé","Sonido apagado","Suono spento","Звук выкл","Звук вимк","Звук изкл","Звук искљ","Ήχος όχι" } },
|
||||||
{ "TCP on", { 0, "TCP aan","TCP ein","TCP activé","TCP activado","TCP acceso","TCP вкл","TCP увімк","TCP вкл","TCP укљ","TCP ναι" } },
|
{ "TCP on", { 0, "TCP aan","TCP ein","TCP activé","TCP activado","TCP acceso","TCP вкл","TCP увімк","TCP вкл","TCP укљ","TCP ναι" } },
|
||||||
{ "TCP off", { 0, "TCP uit","TCP aus","TCP désactivé","TCP apagado","TCP spento","TCP выкл","TCP вимк","TCP изкл","TCP искљ","TCP όχι" } },
|
{ "TCP off", { 0, "TCP uit","TCP aus","TCP désactivé","TCP apagado","TCP spento","TCP выкл","TCP вимк","TCP изкл","TCP искљ","TCP όχι" } },
|
||||||
{ "Welcome to Meshcomod", { 0, "Welkom bij Meshcomod","Willkommen bei Meshcomod","Bienvenue sur Meshcomod","Bienvenido a Meshcomod","Benvenuto in Meshcomod","Добро пожаловать в Meshcomod","Ласкаво просимо до Meshcomod","Добре дошли в Meshcomod","Добро дошли у Meshcomod","Καλώς ήρθατε στο Meshcomod" } },
|
{ "Welcome to WADAMESH", { 0, "Welkom bij WADAMESH","Willkommen bei WADAMESH","Bienvenue sur WADAMESH","Bienvenido a WADAMESH","Benvenuto in WADAMESH","Добро пожаловать в WADAMESH","Ласкаво просимо до WADAMESH","Добре дошли в WADAMESH","Добро дошли у WADAMESH","Καλώς ήρθατε στο WADAMESH" } },
|
||||||
{ "Get Started", { 0, "Aan de slag","Loslegen","Commencer","Empezar","Inizia","Начать","Почати","Започни","Започни","Ξεκινήστε" } },
|
{ "Get Started", { 0, "Aan de slag","Loslegen","Commencer","Empezar","Inizia","Начать","Почати","Започни","Започни","Ξεκινήστε" } },
|
||||||
{ "Choose your name", { 0, "Kies je naam","Wählen Sie Ihren Namen","Choisissez votre nom","Elige tu nombre","Scegli il tuo nome","Выберите имя","Виберіть ім'я","Изберете името си","Изаберите своје име","Επιλέξτε το όνομά σας" } },
|
{ "Choose your name", { 0, "Kies je naam","Wählen Sie Ihren Namen","Choisissez votre nom","Elige tu nombre","Scegli il tuo nome","Выберите имя","Виберіть ім'я","Изберете името си","Изаберите своје име","Επιλέξτε το όνομά σας" } },
|
||||||
{ "Your name", { 0, "Je naam","Ihr Name","Votre nom","Tu nombre","Il tuo nome","Ваше имя","Ваше ім'я","Вашето име","Ваше име","Το όνομά σας" } },
|
{ "Your name", { 0, "Je naam","Ihr Name","Votre nom","Tu nombre","Il tuo nome","Ваше имя","Ваше ім'я","Вашето име","Ваше име","Το όνομά σας" } },
|
||||||
|
|||||||
Reference in new issue
Block a user