Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c278a06ca1 | ||
|
|
828f7ce821 | ||
|
|
ca5874fe70 | ||
|
|
07ac7ba457 | ||
|
|
942725a047 | ||
|
|
34e6714785 | ||
|
|
82023d36b3 |
+49
-8
@@ -1,14 +1,31 @@
|
||||
# CI and releases (docs/milestones/R1.md).
|
||||
# A push to main: the host tests, with their coverage of lib/, and the README's badges
|
||||
# published to the branch `badges`.
|
||||
# A pull request: the same tests and coverage, then the firmware.
|
||||
# A pull request: the same tests and coverage, then the firmware (from the build cache).
|
||||
# A branch's pushes run nothing by themselves: its pull request runs, once.
|
||||
# A tag v*: all of it, then a Gitea release with the signed Update File.
|
||||
# A tag v*: the tests, then the firmware built once, clean, signed and published as a
|
||||
# Gitea release.
|
||||
# Run by hand: the release of a tag that exists already (the ones from before CI).
|
||||
#
|
||||
# The job runs in a plain Python image, as scripts/ci.sh does on a developer's machine, with the
|
||||
# toolchains in a Docker volume the runner allows (container.valid_volumes: roro9stack-pio): that
|
||||
# volume is the cache. No JavaScript actions, so the image needs no Node: the checkout is git.
|
||||
#
|
||||
# What the volume keeps between runs, and what makes each go stale (issue #74, R1.md):
|
||||
# /pio/packages, /pio/platforms toolchains and the framework: by their versions in platformio.ini
|
||||
# .../framework-arduinoespressif32-libs/.roro-sdkconfig.defaults
|
||||
# the mark that the framework is already rebuilt with our SDK settings: the
|
||||
# project's sdkconfig.defaults, which isn't in git, so that every fresh
|
||||
# checkout rebuilt the framework (260 s). The platform checks its hash
|
||||
# against platformio.ini's settings, and rebuilds if they differ. It is kept
|
||||
# inside the libraries it describes, so it goes when they are reinstalled.
|
||||
# /pio/ci/build-cache PlatformIO's build cache (SCons): objects by the signature of their
|
||||
# sources and command line. For pull requests only: a release compiles
|
||||
# its own sources from nothing.
|
||||
# /pio/ci/ccache the host tests' objects (they're built for coverage, which the build
|
||||
# cache can't keep: it would lose the .gcno files)
|
||||
# /pio/ci/venv PlatformIO and gcovr: delete the folder to upgrade them
|
||||
# To start from nothing (a slow run, about 7 minutes): delete /pio/ci and that .roro-sdkconfig.defaults file.
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
@@ -37,13 +54,23 @@ jobs:
|
||||
env:
|
||||
PLATFORMIO_CORE_DIR: /pio
|
||||
RORO_NO_DOCKER: 1
|
||||
SDK_MARK: /pio/packages/framework-arduinoespressif32-libs/.roro-sdkconfig.defaults
|
||||
CCACHE_DIR: /pio/ci/ccache
|
||||
CCACHE_MAXSIZE: 1G
|
||||
steps:
|
||||
- name: Tools
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq --no-install-recommends git build-essential openssl >/dev/null
|
||||
pip install -q --no-cache-dir --root-user-action=ignore platformio gcovr
|
||||
pio --version; df -h /pio | tail -1; ls /pio | head
|
||||
apt-get install -y -qq --no-install-recommends git build-essential openssl ccache >/dev/null
|
||||
mkdir -p /pio/ci
|
||||
if [ ! -x /pio/ci/venv/bin/pio ]; then
|
||||
python -m venv /pio/ci/venv
|
||||
/pio/ci/venv/bin/pip install -q --no-cache-dir platformio gcovr
|
||||
fi
|
||||
ln -sf /pio/ci/venv/bin/pio /pio/ci/venv/bin/gcovr /usr/local/bin/
|
||||
pio --version; df -h /pio | tail -1; du -sh /pio/ci/* 2>/dev/null || true
|
||||
# The build cache only grows: start it again past 3 GB (a full set of objects is 160 MB, and each run adds about 40).
|
||||
if [ "$(du -sm /pio/ci/build-cache 2>/dev/null | cut -f1)" -gt 3072 ] 2>/dev/null; then rm -rf /pio/ci/build-cache; fi
|
||||
|
||||
- name: Check out
|
||||
run: |
|
||||
@@ -57,11 +84,20 @@ jobs:
|
||||
|
||||
- name: Host tests, and their coverage of lib/
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: scripts/coverage.sh
|
||||
run: |
|
||||
export PATH="/usr/lib/ccache:$PATH" # gcc and g++ through ccache
|
||||
scripts/coverage.sh
|
||||
ccache -s | grep -E 'Hits|Misses' | head -2
|
||||
|
||||
# A pull request only: a tag's firmware is built once, by the release step below.
|
||||
- name: The firmware
|
||||
if: github.event_name == 'pull_request' || github.ref_type == 'tag'
|
||||
run: scripts/ci.sh builds
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
PLATFORMIO_BUILD_CACHE_DIR: /pio/ci/build-cache
|
||||
run: |
|
||||
[ ! -f "$SDK_MARK" ] || cp "$SDK_MARK" sdkconfig.defaults
|
||||
scripts/ci.sh builds
|
||||
cp sdkconfig.defaults "$SDK_MARK" # what the framework in the volume is rebuilt with, now
|
||||
|
||||
# The README's badges are files on a branch of their own, replaced at each push to main and
|
||||
# at each tag (the release badge says which tag is the latest)
|
||||
@@ -103,7 +139,12 @@ jobs:
|
||||
trap 'rm -f "$RORO_OTA_KEY"' EXIT
|
||||
printf '%s\n' "$OTA_SIGNING_KEY" > "$RORO_OTA_KEY"
|
||||
umask 022
|
||||
# The framework rebuilt with our settings is reused if it matches (the platform checks);
|
||||
# the release's own sources are compiled from nothing, with no build cache.
|
||||
[ ! -f "$SDK_MARK" ] || cp "$SDK_MARK" /tmp/release-src/sdkconfig.defaults
|
||||
scripts/release_build.sh /tmp/release-src dist
|
||||
# An old tag has no SDK settings of its own, and no sdkconfig.defaults afterwards: nothing to mark.
|
||||
[ ! -f /tmp/release-src/sdkconfig.defaults ] || [ ! -d "$(dirname "$SDK_MARK")" ] || cp /tmp/release-src/sdkconfig.defaults "$SDK_MARK"
|
||||
|
||||
- name: Publish the release
|
||||
if: steps.release.outputs.tag != ''
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
#
|
||||
# It runs when the site, or a document the site is built from, changes (a pull request, or a push to
|
||||
# main); the firmware workflow (ci.yml) skips a change that touches only these files. A change that
|
||||
# touches both runs both. src/main.cpp is here too: the site's command reference is generated from the
|
||||
# firmware's own `help` text, and this job checks that it is still current.
|
||||
# touches both runs both. src/main.cpp and lib/core/src/app_keys.h are here too: the site's command
|
||||
# reference and its key tables are generated from them, and this job checks that they are still current.
|
||||
name: Site
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md', 'src/main.cpp', '.gitea/workflows/site.yml']
|
||||
paths: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md', 'src/main.cpp', 'lib/core/src/app_keys.h', '.gitea/workflows/site.yml']
|
||||
pull_request:
|
||||
paths: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md', 'src/main.cpp', '.gitea/workflows/site.yml']
|
||||
paths: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md', 'src/main.cpp', 'lib/core/src/app_keys.h', '.gitea/workflows/site.yml']
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -12,3 +12,6 @@ sdkconfig.*
|
||||
|
||||
# The built site (site/config.toml sends it here)
|
||||
/public/
|
||||
|
||||
# The version, written by scripts/version.py before each build
|
||||
lib/version/src/version_generated.h
|
||||
|
||||
@@ -10,7 +10,7 @@ A multi-app firmware for the **M5Stack Cardputer ADV** with the **Cap LoRa-1262*
|
||||
|
||||
## On the device: one key
|
||||
|
||||
**Fn+h, on any screen, lists the keys that work there** (`?` does the same outside a text field). No screen names its keys itself (docs/milestones/U1.md): each App declares them for the state it is in, and the help panel shows them, followed by the ones that work everywhere.
|
||||
**Fn+h, on any screen, lists the keys that work there** (`?` does the same outside a text field). No screen names its keys itself (docs/milestones/U1.md). Every screen's keys are one table in `lib/core/src/app_keys.h`: the help panel shows the table of the state an App is in, and the website's key tables are generated from the same file.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -26,7 +26,7 @@ This runs the host-side unit tests (`test/`, `native` environment), then builds
|
||||
|
||||
`scripts/coverage.sh` runs the same tests with coverage counters and writes a line-by-line report to `.pio/coverage/index.html`. The badge above is its figure for `main`: the share of the lines of `lib/` that the host tests run. `lib/` is the logic that compiles on a PC; `lib/SD` (the card's driver) and `src/` (the Apps, the Services, everything that needs the device) have no host tests and aren't in that figure.
|
||||
|
||||
The framework is rebuilt with the TLS settings in `platformio.ini` (`custom_sdkconfig`, ADR 0006), so the first build after a fresh checkout takes about 4 minutes; later builds take under a minute.
|
||||
The framework is rebuilt with the TLS settings in `platformio.ini` (`custom_sdkconfig`, ADR 0006), so the first build after a fresh checkout takes about 6 minutes; later builds take about 15 seconds when little has changed. The platform knows the framework is already rebuilt by `sdkconfig.defaults` in the project folder, which it writes and git ignores: delete it and the next build rebuilds the framework. CI keeps that file, a build cache and ccache in its volume (docs/milestones/R1.md).
|
||||
|
||||
## CI and releases
|
||||
|
||||
|
||||
@@ -172,3 +172,62 @@ The issue asked for a token that could be set, so that Debug Builds could be pub
|
||||
**One false alarm:** after the tests the console "wouldn't come back on". It was off: the setting had been left off by `debug off`, and the page's "Switch it on?" dialog opens on **Cancel**, so Enter twice leaves it off. Nothing was lost.
|
||||
|
||||
**Not checked:** `scripts/flash.sh --debug` over USB (no device on USB here); "New token" and "Type a token" from the page (the code paths are the ones `debug token` uses, which need the cable); the Toast itself on screen (the console is closed while it shows; its Notification is in the log); free memory with the console off, which only the serial port could say (the static figures above are the evidence); fetching a release's ELF, which needs a crash on a released version.
|
||||
|
||||
## CI that doesn't rebuild the world (issue #74)
|
||||
|
||||
A pull request's run took over seven minutes, a tag's thirteen and a half. The goal: a firmware build under a minute.
|
||||
|
||||
### Where the time went (run 81, a pull request, 2026-10-06)
|
||||
|
||||
| Step | Time |
|
||||
|---|---|
|
||||
| Tools (apt, pip) | 15 s |
|
||||
| Check out | 2 s |
|
||||
| Host tests and coverage | 55 s |
|
||||
| **The firmware** | **358 s** |
|
||||
| of which: CMake configuring ESP-IDF | 87 s |
|
||||
| of which: compiling ESP-IDF's libraries | 171 s |
|
||||
| of which: our own build (the Arduino core, the libraries, `src/`) | 91 s |
|
||||
|
||||
A tag's run did the firmware step, then built the same commit again for the release: twice 356 s.
|
||||
|
||||
### Why the framework was rebuilt every time
|
||||
|
||||
The framework is rebuilt with our SDK settings (ADR 0006), and the rebuilt libraries stay in the toolchain volume. But the platform decides whether they match by reading **`sdkconfig.defaults` in the project folder**, whose first line carries a hash of the settings. That file is generated, and not in git. A fresh checkout has none, so the platform concluded "different settings", **reinstalled the framework and rebuilt it**: 260 seconds, at every run, to arrive at the libraries that were already there. On a developer's machine the file is simply still there from the last build, which is why nobody saw it.
|
||||
|
||||
### What changed
|
||||
|
||||
| Change | Effect |
|
||||
|---|---|
|
||||
| **The file is kept in the volume, inside the libraries it describes** (`framework-arduinoespressif32-libs/.roro-sdkconfig.defaults`), copied into the checkout before a build and back after one that passed. The platform still checks its hash against `platformio.ini`: changed settings rebuild, as they must. Kept there and not beside them, it disappears when the libraries are reinstalled, so it can't describe libraries that are gone | 358 s to 92 s |
|
||||
| **The version is no longer a `-D` on every compiler command line.** `scripts/version.py` writes `lib/version/src/version_generated.h` (not in git, written only when it changes), read by one file. Before, every commit recompiled everything, on a developer's machine too, and no cache could have helped | A rebuild with nothing changed: 77 s to 13 s, locally |
|
||||
| **PlatformIO's build cache** (`PLATFORMIO_BUILD_CACHE_DIR`, SCons's CacheDir) in the volume, for the firmware of pull requests: objects by the signature of their sources and command line | 92 s to 26 s, with a new version and one changed file |
|
||||
| **ccache for the host tests.** They are built with coverage counters, and the build cache would return objects without their `.gcno` files; ccache keeps both | 49 s to 33 s. What's left is PlatformIO starting 51 test programs |
|
||||
| **A tag builds its firmware once**, in the release step | minus 6 minutes |
|
||||
| PlatformIO and gcovr in a virtual environment in the volume | a few seconds |
|
||||
|
||||
**A release compiles its own sources from nothing:** it reuses the rebuilt framework (the platform checks the hash) but not the build cache, so no published file contains an object that came from another commit's build.
|
||||
|
||||
### Measured (a development machine, fresh copies of the tree, the same volume)
|
||||
|
||||
| | Before | After |
|
||||
|---|---|---|
|
||||
| The firmware, fresh checkout, nothing cached for it | 358 s | 82 s (it fills the cache) |
|
||||
| The firmware, fresh checkout, a new version and one file changed | 358 s | **27 s** |
|
||||
| Host tests and coverage | 49 s | 33 s |
|
||||
| Rebuilding locally with nothing changed | 77 s | 13 s |
|
||||
|
||||
### Measured on the runner (pull request #76, 2026-10-07)
|
||||
|
||||
| Run | Tools | Tests and coverage | The firmware | The whole job |
|
||||
|---|---|---|---|---|
|
||||
| Before (run 81) | 15 s | 55 s | 358 s | 434 s |
|
||||
| The first with the new workflow: no mark yet, the framework is rebuilt once more and the caches fill | 16 s | 59 s | 354 s | 431 s |
|
||||
| The next commit (only the workflow changed) | 10 s | 36 s | **51 s** | **100 s** |
|
||||
| The same commit again | 10 s | 36 s | **18 s** | **66 s** |
|
||||
|
||||
In the 51-second run, 245 objects came from the cache and 43 were compiled: `version.cpp`, as expected, and all 42 files of `src/`, which had not changed. In the run after it, all 290 came from the cache. So the objects of `src/` made by the run that rebuilt the framework were not reusable by a normal run, and those of a normal run are: the two-pass build that rebuilds the framework compiles `src/` with something different on its command line. It costs one 51-second run after each framework rebuild, which is rare; I did not look for what differs.
|
||||
|
||||
The firmware step with everything cached is 18 seconds: the libraries are downloaded and unpacked (4 s), the dependency scan (5 s), fetching 290 objects, the link and the image (the last 11 s). A pull request that changes a few files should land between that and 51 seconds.
|
||||
|
||||
**What it costs:** the build cache grows by about 40 MB a run (each linked firmware is kept) and is started again past 3 GB; ccache is held to 1 GB.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# U1 — Look and feel
|
||||
|
||||
**Status:** in progress. The help key (issue #69) is built and checked on the device, in a pull request. Screen recording (#17) and the rest of the milestone are not started.
|
||||
**Status:** in progress. The help key (issue #69) is merged; the website's key tables generated from the same lists (issue #72) are in a pull request. Screen recording (#17) and the rest of the milestone are not started.
|
||||
|
||||
**Goal:** the interface is consistent and uncrowded: the same thing is done the same way on every screen, and the 135 pixels of height go to content.
|
||||
|
||||
@@ -37,4 +37,10 @@ Every screen used to say something about its keys, differently: a footer of abbr
|
||||
| On the device, `key help` and a screenshot | The Launcher and all nine Apps, and these states: Storage scrolled, System's tasks, a Settings text field, Wi-Fi Tools' networks. The panel is titled with the scope, lists the right keys, scrolls, and closes on Tab |
|
||||
| The one-time Toast | `notification: Fn+h: the keys of any screen` on the first start after the update |
|
||||
|
||||
### One source for the device and the website (issue #72)
|
||||
|
||||
The lists first lived in each App's `help()`, as code. They are now **data, in one file**: `lib/core/src/app_keys.h`, 52 constant tables, each under a comment `// id: Title`. An App's `help()` picks the table of the state it is in. `site/tools/gen_dev_docs.py` reads the same file and writes `site/data/keys.toml`; the `keys` shortcode shows a screen's tables on its guide page, and `/guide/keys/` shows all of them. The Site job fails when the data file is out of date, or when a page asks for a table that doesn't exist, and it now runs when `app_keys.h` changes. A key added to an App shows up on the website without anyone editing a page.
|
||||
|
||||
Three rows lost their second wording on the way, since a table is constant: GNSS's `Tab` and `r`, and the Scanner's `c`, now say both things they do ("record a Track, or stop it") instead of the one that applies. The guide pages keep their written tables too, where they say more than a key list can; those can still drift, and the generated ones under them are the reference.
|
||||
|
||||
**Not checked:** the real Fn+h and `?` on the keyboard (the mapper is host-tested; the device was driven with `key help`); the Setup screens, which only a device that was never set up shows, so their new text has not been seen on a screen; and the states that need something to happen first (a dialog, a copy in progress, a Gemini prompt, a packet's details): their lists were read against the key handling, not looked at.
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "key_help.h"
|
||||
|
||||
// Every key of every screen, as data (issues #69 and #72). The help panel (Fn+h) shows the table of
|
||||
// the state an App is in; site/tools/gen_dev_docs.py reads this file to write the website's key
|
||||
// tables, so the two can't drift.
|
||||
//
|
||||
// The format is read by that script, so keep it: a comment `// id: Title`, then one table,
|
||||
// inline constexpr KeyHelp kName[] = {
|
||||
// {"keys", "what they do"},
|
||||
// };
|
||||
// with one row a line and plain string literals. `id` is what a guide page asks for.
|
||||
namespace roro::keys {
|
||||
|
||||
template <size_t N>
|
||||
inline void add(std::vector<KeyHelp>& out, const KeyHelp (&rows)[N]) {
|
||||
out.insert(out.end(), rows, rows + N);
|
||||
}
|
||||
|
||||
// everywhere: Everywhere
|
||||
inline constexpr KeyHelp kEverywhere[] = {
|
||||
{"`", "back"},
|
||||
{"Fn `", "home, the Launcher"},
|
||||
{"; . , /", "arrows (Fn+ while typing)"},
|
||||
{"Fn h ?", "these keys (? not typing)"},
|
||||
};
|
||||
|
||||
// dialog: A question
|
||||
inline constexpr KeyHelp kDialog[] = {
|
||||
{", /", "the other answer"},
|
||||
{"Enter", "choose it"},
|
||||
{"`", "cancel"},
|
||||
};
|
||||
|
||||
// text: A text field
|
||||
inline constexpr KeyHelp kText[] = {
|
||||
{"Enter", "save"},
|
||||
{"`", "cancel"},
|
||||
{"Del", "delete backwards"},
|
||||
{"Fn , /", "move the cursor"},
|
||||
{"opt ' e", "an accent: \xC3\xA9"},
|
||||
};
|
||||
|
||||
// launcher: The Launcher
|
||||
inline constexpr KeyHelp kLauncher[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "open the App"},
|
||||
};
|
||||
|
||||
// setup: Setup, a step
|
||||
inline constexpr KeyHelp kSetup[] = {
|
||||
{"Enter", "continue"},
|
||||
{"`", "the step before"},
|
||||
};
|
||||
|
||||
// setup-choice: Setup, a choice
|
||||
inline constexpr KeyHelp kSetupChoice[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "choose it, next step"},
|
||||
{"`", "the step before"},
|
||||
};
|
||||
|
||||
// setup-text: Setup, a name
|
||||
inline constexpr KeyHelp kSetupText[] = {
|
||||
{"Enter", "next step"},
|
||||
{"`", "the step before"},
|
||||
{"Del", "delete backwards"},
|
||||
{"Fn , /", "move the cursor"},
|
||||
{"opt ' e", "an accent: \xC3\xA9"},
|
||||
};
|
||||
|
||||
// irc: IRC
|
||||
inline constexpr KeyHelp kIrc[] = {
|
||||
{"Enter", "send the line"},
|
||||
{"Tab", "the next buffer"},
|
||||
{"Alt ; .", "scroll back, forward"},
|
||||
{"Fn ; .", "lines you sent before"},
|
||||
{"Fn , /", "move the cursor"},
|
||||
{"Del", "delete backwards"},
|
||||
{"/settings", "server, nick, passwords"},
|
||||
{"/join #x", "join a channel"},
|
||||
{"/part", "leave it"},
|
||||
{"/msg nick", "a private chat"},
|
||||
{"/me", "an action"},
|
||||
{"/nick", "change your nick"},
|
||||
{"/topic", "see or set the topic"},
|
||||
{"/names", "who is there"},
|
||||
{"/quit", "disconnect, and stay so"},
|
||||
{"/raw", "a line as it is"},
|
||||
{"`", "leave: IRC stays connected"},
|
||||
};
|
||||
|
||||
// irc-settings: IRC settings
|
||||
inline constexpr KeyHelp kIrcSettings[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "edit, switch, or save"},
|
||||
{"`", "leave without saving"},
|
||||
};
|
||||
|
||||
// irc-field: IRC, a setting
|
||||
inline constexpr KeyHelp kIrcField[] = {
|
||||
{"Enter", "keep it"},
|
||||
{"`", "cancel"},
|
||||
{"Del", "delete backwards"},
|
||||
{"Fn , /", "move the cursor"},
|
||||
};
|
||||
|
||||
// wifi-tools: Wi-Fi Tools
|
||||
inline constexpr KeyHelp kWifiTools[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "open"},
|
||||
};
|
||||
|
||||
// wifi-networks: Networks nearby
|
||||
inline constexpr KeyHelp kWifiNetworks[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "track its signal"},
|
||||
{"s", "sort: signal, channel, name"},
|
||||
{"o", "open networks only"},
|
||||
{"h", "hide the hidden ones"},
|
||||
{"w", "strong ones only"},
|
||||
{"l", "log the scans to the card"},
|
||||
};
|
||||
|
||||
// wifi-tracker: Signal tracker
|
||||
inline constexpr KeyHelp kWifiTracker[] = {
|
||||
{"m", "clicks on or off"},
|
||||
};
|
||||
|
||||
// gnss: GNSS
|
||||
inline constexpr KeyHelp kGnss[] = {
|
||||
{"Tab", "the position, or the sky"},
|
||||
{"r", "record a Track, or stop it"},
|
||||
};
|
||||
|
||||
// gemini: Gemini, a page
|
||||
inline constexpr KeyHelp kGemini[] = {
|
||||
{"Tab", "the next link"},
|
||||
{"Aa Tab", "the link before"},
|
||||
{"Enter", "follow the link"},
|
||||
{"` Del", "the page before"},
|
||||
{"; .", "scroll"},
|
||||
{"Space", "a page down"},
|
||||
{", /", "sideways, in wide blocks"},
|
||||
{"g", "type an address"},
|
||||
{"b", "bookmark this page"},
|
||||
{"s", "save the page to the card"},
|
||||
{"S", "...with the pages it links to"},
|
||||
};
|
||||
|
||||
// gemini-saved: Gemini, a Saved Page
|
||||
inline constexpr KeyHelp kGeminiSaved[] = {
|
||||
{"Tab", "the next link"},
|
||||
{"Aa Tab", "the link before"},
|
||||
{"Enter", "follow the link"},
|
||||
{"` Del", "the page before"},
|
||||
{"; .", "scroll"},
|
||||
{"Space", "a page down"},
|
||||
{", /", "sideways, in wide blocks"},
|
||||
{"g", "type an address"},
|
||||
{"b", "bookmark this page"},
|
||||
{"r", "refresh this Saved Page"},
|
||||
{"d", "delete this Saved Page"},
|
||||
};
|
||||
|
||||
// gemini-address: Gemini, an address
|
||||
inline constexpr KeyHelp kGeminiAddress[] = {
|
||||
{"Enter", "go there"},
|
||||
{"`", "cancel"},
|
||||
{"Del", "delete backwards"},
|
||||
{"Fn , /", "move the cursor"},
|
||||
};
|
||||
|
||||
// gemini-answer: Gemini, an answer to a page
|
||||
inline constexpr KeyHelp kGeminiAnswer[] = {
|
||||
{"Enter", "send it"},
|
||||
{"`", "cancel"},
|
||||
{"Del", "delete backwards"},
|
||||
{"Fn , /", "move the cursor"},
|
||||
};
|
||||
|
||||
// lora: LoRa Scanner, the packets
|
||||
inline constexpr KeyHelp kLora[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "the packet's details"},
|
||||
{"p", "pick a Meshtastic preset"},
|
||||
{"c", "start a Capture, or stop it"},
|
||||
{"Tab", "the Sweep"},
|
||||
};
|
||||
|
||||
// lora-packet: LoRa Scanner, a packet
|
||||
inline constexpr KeyHelp kLoraPacket[] = {
|
||||
{"; .", "scroll"},
|
||||
{"Enter", "back to the list"},
|
||||
};
|
||||
|
||||
// lora-presets: LoRa Scanner, the presets
|
||||
inline constexpr KeyHelp kLoraPresets[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "listen with this preset"},
|
||||
};
|
||||
|
||||
// lora-sweep: LoRa Scanner, the Sweep
|
||||
inline constexpr KeyHelp kLoraSweep[] = {
|
||||
{"Tab", "the Sniffer"},
|
||||
};
|
||||
|
||||
// storage: Storage, a folder
|
||||
inline constexpr KeyHelp kStorage[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "open the folder or the file"},
|
||||
{", /", "a page up, down"},
|
||||
{"c x", "copy, cut"},
|
||||
{"v", "paste here"},
|
||||
{"r", "rename"},
|
||||
{"d Del", "delete, after asking"},
|
||||
{"n", "a new folder"},
|
||||
{"i", "details: size, date, type"},
|
||||
{"s", "sort: name, date, size"},
|
||||
{"m", "Maintenance: clean-up, erase"},
|
||||
{"`", "the folder above"},
|
||||
};
|
||||
|
||||
// storage-details: Storage, an item's details
|
||||
inline constexpr KeyHelp kStorageDetails[] = {
|
||||
{"; .", "scroll"},
|
||||
{"Enter", "back to the folder"},
|
||||
};
|
||||
|
||||
// storage-name: Storage, a name
|
||||
inline constexpr KeyHelp kStorageName[] = {
|
||||
{"Enter", "rename it, or make the folder"},
|
||||
{"`", "cancel"},
|
||||
{"Del", "delete backwards"},
|
||||
{"Fn , /", "move the cursor"},
|
||||
};
|
||||
|
||||
// storage-busy: Storage, while it copies or deletes
|
||||
inline constexpr KeyHelp kStorageBusy[] = {
|
||||
{"`", "stop the copy or the delete"},
|
||||
};
|
||||
|
||||
// maintenance: Storage, Maintenance
|
||||
inline constexpr KeyHelp kMaintenance[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "open, or choose"},
|
||||
};
|
||||
|
||||
// viewer-text: A file, as text
|
||||
inline constexpr KeyHelp kViewerText[] = {
|
||||
{"; .", "a line up, down"},
|
||||
{", /", "a page up, down"},
|
||||
{"t b", "the top, the end"},
|
||||
{"e", "edit it (up to 16 KB)"},
|
||||
{"Tab", "the file as hex, or back"},
|
||||
};
|
||||
|
||||
// viewer-hex: A file, as hex
|
||||
inline constexpr KeyHelp kViewerHex[] = {
|
||||
{"; .", "a line up, down"},
|
||||
{", /", "a page up, down"},
|
||||
{"t b", "the top, the end"},
|
||||
{"Tab", "the file as text, or back"},
|
||||
};
|
||||
|
||||
// viewer-pcap: A Capture
|
||||
inline constexpr KeyHelp kViewerPcap[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "the packet"},
|
||||
{", /", "a page up, down"},
|
||||
{"Tab", "the file as hex"},
|
||||
};
|
||||
|
||||
// viewer-packet: A Capture's packet
|
||||
inline constexpr KeyHelp kViewerPacket[] = {
|
||||
{"; .", "scroll"},
|
||||
{"Enter", "back to the packets"},
|
||||
};
|
||||
|
||||
// viewer-gpx: A Track
|
||||
inline constexpr KeyHelp kViewerGpx[] = {
|
||||
{"Tab", "the file as text"},
|
||||
};
|
||||
|
||||
// viewer-ota: An Update File
|
||||
inline constexpr KeyHelp kViewerOta[] = {
|
||||
{"Enter", "install it, if it's genuine"},
|
||||
{"Tab", "the file as hex"},
|
||||
};
|
||||
|
||||
// notes: Notes, the list
|
||||
inline constexpr KeyHelp kNotes[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "open the note"},
|
||||
{", /", "a page up, down"},
|
||||
{"n", "a new note"},
|
||||
{"r", "rename its file"},
|
||||
{"d Del", "delete it"},
|
||||
{"s", "sort: newest, or by name"},
|
||||
};
|
||||
|
||||
// notes-editor: Notes, the editor
|
||||
inline constexpr KeyHelp kNotesEditor[] = {
|
||||
{"Enter", "a new line"},
|
||||
{"Del", "delete backwards"},
|
||||
{"Tab", "two spaces"},
|
||||
{"Fn ; . , /", "move the cursor"},
|
||||
{"Alt Fn ; .", "a page up, down"},
|
||||
{"Ctrl a e", "start, end of the line"},
|
||||
{"opt ' e", "an accent: \xC3\xA9"},
|
||||
{"`", "done: it saves by itself"},
|
||||
};
|
||||
|
||||
// notes-name: Notes, a file name
|
||||
inline constexpr KeyHelp kNotesName[] = {
|
||||
{"Enter", "rename the file"},
|
||||
{"`", "cancel"},
|
||||
{"Del", "delete backwards"},
|
||||
{"Fn , /", "move the cursor"},
|
||||
};
|
||||
|
||||
// system: System, any view
|
||||
inline constexpr KeyHelp kSystem[] = {
|
||||
{"Tab", "the next view"},
|
||||
{"Aa Tab", "the view before"},
|
||||
};
|
||||
|
||||
// system-tasks: System, the tasks
|
||||
inline constexpr KeyHelp kSystemTasks[] = {
|
||||
{"Tab", "the next view"},
|
||||
{"Aa Tab", "the view before"},
|
||||
{"; .", "scroll"},
|
||||
{"s", "sort: cpu, stack, name"},
|
||||
};
|
||||
|
||||
// system-system: System, the system view
|
||||
inline constexpr KeyHelp kSystemSystem[] = {
|
||||
{"Tab", "the next view"},
|
||||
{"Aa Tab", "the view before"},
|
||||
{"; .", "scroll"},
|
||||
};
|
||||
|
||||
// settings: Settings
|
||||
inline constexpr KeyHelp kSettings[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "edit, or open the page"},
|
||||
{", /", "change a switch or a slider"},
|
||||
};
|
||||
|
||||
// settings-choice: Settings, a choice
|
||||
inline constexpr KeyHelp kSettingsChoice[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "choose it"},
|
||||
};
|
||||
|
||||
// wifi: Settings, Wi-Fi
|
||||
inline constexpr KeyHelp kWifi[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "open, or change"},
|
||||
{", /", "switch Wi-Fi on or off"},
|
||||
};
|
||||
|
||||
// wifi-servers: Settings, DNS and NTP
|
||||
inline constexpr KeyHelp kWifiServers[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "edit"},
|
||||
{", /", "Always use my DNS: on, off"},
|
||||
};
|
||||
|
||||
// wifi-network: Settings, a saved network
|
||||
inline constexpr KeyHelp kWifiNetwork[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "edit, or forget"},
|
||||
{", /", "Automatic or Fixed"},
|
||||
};
|
||||
|
||||
// wifi-status: Settings, the Wi-Fi status
|
||||
inline constexpr KeyHelp kWifiStatus[] = {
|
||||
{"Enter", "back"},
|
||||
};
|
||||
|
||||
// wifi-scan: Settings, adding a network
|
||||
inline constexpr KeyHelp kWifiScan[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "choose this network"},
|
||||
};
|
||||
|
||||
// wifi-name: Settings, a hidden network's name
|
||||
inline constexpr KeyHelp kWifiName[] = {
|
||||
{"Enter", "next: the password"},
|
||||
{"`", "cancel"},
|
||||
{"Del", "delete backwards"},
|
||||
{"Fn , /", "move the cursor"},
|
||||
};
|
||||
|
||||
// firmware: Settings, Firmware
|
||||
inline constexpr KeyHelp kFirmware[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "check, open, or install"},
|
||||
{"c", "look for a newer release"},
|
||||
};
|
||||
|
||||
// firmware-release: Settings, a release
|
||||
inline constexpr KeyHelp kFirmwareRelease[] = {
|
||||
{"; .", "scroll"},
|
||||
{"Enter", "install it"},
|
||||
{"c", "check again"},
|
||||
};
|
||||
|
||||
// firmware-older: Settings, older releases
|
||||
inline constexpr KeyHelp kFirmwareOlder[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "its details"},
|
||||
{"c", "read the list again"},
|
||||
};
|
||||
|
||||
// debug-console: Settings, Debug Console
|
||||
inline constexpr KeyHelp kDebugConsole[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "switch, or open"},
|
||||
{", /", "switch the console on or off"},
|
||||
};
|
||||
|
||||
// demo: The widget demo
|
||||
inline constexpr KeyHelp kDemo[] = {
|
||||
{"; .", "up, down"},
|
||||
{"Enter", "try the widget"},
|
||||
};
|
||||
|
||||
} // namespace roro::keys
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "app_manager.h"
|
||||
|
||||
#include "app_keys.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace roro {
|
||||
@@ -60,7 +62,8 @@ void AppManager::handleKey(const KeyEvent& event) {
|
||||
if (event.key == Key::Help) {
|
||||
std::vector<KeyHelp> rows;
|
||||
foreground_->help(rows);
|
||||
help::everywhere(rows);
|
||||
rows.push_back({"Everywhere", nullptr});
|
||||
keys::add(rows, keys::kEverywhere);
|
||||
const char* scope = foreground_->helpTitle();
|
||||
const char* app = foregroundTitle();
|
||||
help_.open(scope ? scope : app ? app : "Launcher", std::move(rows));
|
||||
|
||||
@@ -16,35 +16,6 @@ struct KeyHelp {
|
||||
const char* action;
|
||||
};
|
||||
|
||||
namespace help {
|
||||
|
||||
// Lists that several screens share.
|
||||
inline void list(std::vector<KeyHelp>& out, const char* enter = "open") {
|
||||
out.push_back({"; .", "up, down"});
|
||||
out.push_back({"Enter", enter});
|
||||
}
|
||||
inline void dialog(std::vector<KeyHelp>& out) {
|
||||
out.push_back({", /", "the other answer"});
|
||||
out.push_back({"Enter", "choose it"});
|
||||
out.push_back({"`", "cancel"});
|
||||
}
|
||||
inline void textEntry(std::vector<KeyHelp>& out, const char* enter = "save") {
|
||||
out.push_back({"Enter", enter});
|
||||
out.push_back({"`", "cancel"});
|
||||
out.push_back({"Del", "delete backwards"});
|
||||
out.push_back({"Fn , /", "move the cursor"});
|
||||
out.push_back({"opt ' e", "an accent: \xC3\xA9"});
|
||||
}
|
||||
// What works on every screen: the end of every panel.
|
||||
inline void everywhere(std::vector<KeyHelp>& out) {
|
||||
out.push_back({"Everywhere", nullptr});
|
||||
out.push_back({"`", "back"});
|
||||
out.push_back({"Fn `", "home, the Launcher"});
|
||||
out.push_back({"; . , /", "arrows (Fn+ while typing)"});
|
||||
out.push_back({"Fn h ?", "these keys (? not typing)"});
|
||||
}
|
||||
|
||||
} // namespace help
|
||||
|
||||
// The panel itself: what it lists, and how far it's scrolled. The keys it takes while open are the
|
||||
// arrows; any other key closes it, and none reaches the App.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "version.h"
|
||||
|
||||
// Written by scripts/version.py before each build; not in git.
|
||||
#if __has_include("version_generated.h")
|
||||
#include "version_generated.h"
|
||||
#endif
|
||||
#ifndef RORO_VERSION
|
||||
#define RORO_VERSION "unknown"
|
||||
#endif
|
||||
|
||||
namespace roro {
|
||||
|
||||
const char* versionString() { return RORO_VERSION; }
|
||||
|
||||
} // namespace roro
|
||||
@@ -1,14 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef RORO_VERSION
|
||||
#define RORO_VERSION "unknown"
|
||||
#endif
|
||||
|
||||
namespace roro {
|
||||
|
||||
constexpr const char* kProductName = "roro9stack";
|
||||
|
||||
// "roro9stack v0.1.0" — used on the boot screen and in About.
|
||||
inline const char* versionString() { return RORO_VERSION; }
|
||||
// "v0.1.0", from `git describe` (scripts/version.py): used on the boot screen and in About.
|
||||
// A function in one file, not a macro on every compiler command line: a new commit then recompiles
|
||||
// that one file, and everything else comes from the build cache (issue #74).
|
||||
const char* versionString();
|
||||
|
||||
} // namespace roro
|
||||
|
||||
+9
-1
@@ -10,7 +10,15 @@ try:
|
||||
except Exception:
|
||||
version = "unknown"
|
||||
|
||||
env.Append(CPPDEFINES=[("RORO_VERSION", '\\"%s\\"' % version)]) # noqa: F821
|
||||
# The version goes into one generated header, read by one file (lib/version/src/version.cpp). As a -D
|
||||
# on every command line it made each new commit recompile everything, and no build cache could help
|
||||
# (issue #74). Written only when it changes, so an unchanged version rebuilds nothing.
|
||||
import os
|
||||
|
||||
_header = os.path.join(env.subst("$PROJECT_DIR"), "lib", "version", "src", "version_generated.h") # noqa: F821
|
||||
_text = '#define RORO_VERSION "%s"\n' % version
|
||||
if not os.path.exists(_header) or open(_header).read() != _text:
|
||||
open(_header, "w").write(_text)
|
||||
|
||||
|
||||
# Keep every build's ELF, named by version and the first 16 hex digits of its SHA-256 (the core dump
|
||||
|
||||
@@ -22,7 +22,7 @@ This runs the host-side unit tests (`test/`, `native` environment), then builds
|
||||
|
||||
`scripts/coverage.sh` runs the same tests with coverage counters and writes a line-by-line report to `.pio/coverage/index.html`. The badge above is its figure for `main`: the share of the lines of `lib/` that the host tests run. `lib/` is the logic that compiles on a PC; `lib/SD` (the card's driver) and `src/` (the Apps, the Services, everything that needs the device) have no host tests and aren't in that figure.
|
||||
|
||||
The framework is rebuilt with the TLS settings in `platformio.ini` (`custom_sdkconfig`, ADR 0006), so the first build after a fresh checkout takes about 4 minutes; later builds take under a minute.
|
||||
The framework is rebuilt with the TLS settings in `platformio.ini` (`custom_sdkconfig`, ADR 0006), so the first build after a fresh checkout takes about 6 minutes; later builds take about 15 seconds when little has changed. The platform knows the framework is already rebuilt by `sdkconfig.defaults` in the project folder, which it writes and git ignores: delete it and the next build rebuilds the framework. CI keeps that file, a build cache and ccache in its volume (docs/milestones/R1.md).
|
||||
|
||||
## CI and releases
|
||||
|
||||
|
||||
@@ -180,3 +180,62 @@ The issue asked for a token that could be set, so that Debug Builds could be pub
|
||||
**One false alarm:** after the tests the console "wouldn't come back on". It was off: the setting had been left off by `debug off`, and the page's "Switch it on?" dialog opens on **Cancel**, so Enter twice leaves it off. Nothing was lost.
|
||||
|
||||
**Not checked:** `scripts/flash.sh --debug` over USB (no device on USB here); "New token" and "Type a token" from the page (the code paths are the ones `debug token` uses, which need the cable); the Toast itself on screen (the console is closed while it shows; its Notification is in the log); free memory with the console off, which only the serial port could say (the static figures above are the evidence); fetching a release's ELF, which needs a crash on a released version.
|
||||
|
||||
## CI that doesn't rebuild the world (issue #74)
|
||||
|
||||
A pull request's run took over seven minutes, a tag's thirteen and a half. The goal: a firmware build under a minute.
|
||||
|
||||
### Where the time went (run 81, a pull request, 2026-10-06)
|
||||
|
||||
| Step | Time |
|
||||
|---|---|
|
||||
| Tools (apt, pip) | 15 s |
|
||||
| Check out | 2 s |
|
||||
| Host tests and coverage | 55 s |
|
||||
| **The firmware** | **358 s** |
|
||||
| of which: CMake configuring ESP-IDF | 87 s |
|
||||
| of which: compiling ESP-IDF's libraries | 171 s |
|
||||
| of which: our own build (the Arduino core, the libraries, `src/`) | 91 s |
|
||||
|
||||
A tag's run did the firmware step, then built the same commit again for the release: twice 356 s.
|
||||
|
||||
### Why the framework was rebuilt every time
|
||||
|
||||
The framework is rebuilt with our SDK settings (ADR 0006), and the rebuilt libraries stay in the toolchain volume. But the platform decides whether they match by reading **`sdkconfig.defaults` in the project folder**, whose first line carries a hash of the settings. That file is generated, and not in git. A fresh checkout has none, so the platform concluded "different settings", **reinstalled the framework and rebuilt it**: 260 seconds, at every run, to arrive at the libraries that were already there. On a developer's machine the file is simply still there from the last build, which is why nobody saw it.
|
||||
|
||||
### What changed
|
||||
|
||||
| Change | Effect |
|
||||
|---|---|
|
||||
| **The file is kept in the volume, inside the libraries it describes** (`framework-arduinoespressif32-libs/.roro-sdkconfig.defaults`), copied into the checkout before a build and back after one that passed. The platform still checks its hash against `platformio.ini`: changed settings rebuild, as they must. Kept there and not beside them, it disappears when the libraries are reinstalled, so it can't describe libraries that are gone | 358 s to 92 s |
|
||||
| **The version is no longer a `-D` on every compiler command line.** `scripts/version.py` writes `lib/version/src/version_generated.h` (not in git, written only when it changes), read by one file. Before, every commit recompiled everything, on a developer's machine too, and no cache could have helped | A rebuild with nothing changed: 77 s to 13 s, locally |
|
||||
| **PlatformIO's build cache** (`PLATFORMIO_BUILD_CACHE_DIR`, SCons's CacheDir) in the volume, for the firmware of pull requests: objects by the signature of their sources and command line | 92 s to 26 s, with a new version and one changed file |
|
||||
| **ccache for the host tests.** They are built with coverage counters, and the build cache would return objects without their `.gcno` files; ccache keeps both | 49 s to 33 s. What's left is PlatformIO starting 51 test programs |
|
||||
| **A tag builds its firmware once**, in the release step | minus 6 minutes |
|
||||
| PlatformIO and gcovr in a virtual environment in the volume | a few seconds |
|
||||
|
||||
**A release compiles its own sources from nothing:** it reuses the rebuilt framework (the platform checks the hash) but not the build cache, so no published file contains an object that came from another commit's build.
|
||||
|
||||
### Measured (a development machine, fresh copies of the tree, the same volume)
|
||||
|
||||
| | Before | After |
|
||||
|---|---|---|
|
||||
| The firmware, fresh checkout, nothing cached for it | 358 s | 82 s (it fills the cache) |
|
||||
| The firmware, fresh checkout, a new version and one file changed | 358 s | **27 s** |
|
||||
| Host tests and coverage | 49 s | 33 s |
|
||||
| Rebuilding locally with nothing changed | 77 s | 13 s |
|
||||
|
||||
### Measured on the runner (pull request #76, 2026-10-07)
|
||||
|
||||
| Run | Tools | Tests and coverage | The firmware | The whole job |
|
||||
|---|---|---|---|---|
|
||||
| Before (run 81) | 15 s | 55 s | 358 s | 434 s |
|
||||
| The first with the new workflow: no mark yet, the framework is rebuilt once more and the caches fill | 16 s | 59 s | 354 s | 431 s |
|
||||
| The next commit (only the workflow changed) | 10 s | 36 s | **51 s** | **100 s** |
|
||||
| The same commit again | 10 s | 36 s | **18 s** | **66 s** |
|
||||
|
||||
In the 51-second run, 245 objects came from the cache and 43 were compiled: `version.cpp`, as expected, and all 42 files of `src/`, which had not changed. In the run after it, all 290 came from the cache. So the objects of `src/` made by the run that rebuilt the framework were not reusable by a normal run, and those of a normal run are: the two-pass build that rebuilds the framework compiles `src/` with something different on its command line. It costs one 51-second run after each framework rebuild, which is rare; I did not look for what differs.
|
||||
|
||||
The firmware step with everything cached is 18 seconds: the libraries are downloaded and unpacked (4 s), the dependency scan (5 s), fetching 290 objects, the link and the image (the last 11 s). A pull request that changes a few files should land between that and 51 seconds.
|
||||
|
||||
**What it costs:** the build cache grows by about 40 MB a run (each linked firmware is kept) and is started again past 3 GB; ccache is held to 1 GB.
|
||||
|
||||
@@ -8,7 +8,7 @@ docs = true
|
||||
source = "docs/milestones/U1.md"
|
||||
tag = "U1"
|
||||
+++
|
||||
**Status:** in progress. The help key (issue #69) is built and checked on the device, in a pull request. Screen recording (#17) and the rest of the milestone are not started.
|
||||
**Status:** in progress. The help key (issue #69) is merged; the website's key tables generated from the same lists (issue #72) are in a pull request. Screen recording (#17) and the rest of the milestone are not started.
|
||||
|
||||
**Goal:** the interface is consistent and uncrowded: the same thing is done the same way on every screen, and the 135 pixels of height go to content.
|
||||
|
||||
@@ -45,4 +45,10 @@ Every screen used to say something about its keys, differently: a footer of abbr
|
||||
| On the device, `key help` and a screenshot | The Launcher and all nine Apps, and these states: Storage scrolled, System's tasks, a Settings text field, Wi-Fi Tools' networks. The panel is titled with the scope, lists the right keys, scrolls, and closes on Tab |
|
||||
| The one-time Toast | `notification: Fn+h: the keys of any screen` on the first start after the update |
|
||||
|
||||
### One source for the device and the website (issue #72)
|
||||
|
||||
The lists first lived in each App's `help()`, as code. They are now **data, in one file**: `lib/core/src/app_keys.h`, 52 constant tables, each under a comment `// id: Title`. An App's `help()` picks the table of the state it is in. `site/tools/gen_dev_docs.py` reads the same file and writes `site/data/keys.toml`; the `keys` shortcode shows a screen's tables on its guide page, and `/guide/keys/` shows all of them. The Site job fails when the data file is out of date, or when a page asks for a table that doesn't exist, and it now runs when `app_keys.h` changes. A key added to an App shows up on the website without anyone editing a page.
|
||||
|
||||
Three rows lost their second wording on the way, since a table is constant: GNSS's `Tab` and `r`, and the Scanner's `c`, now say both things they do ("record a Track, or stop it") instead of the one that applies. The guide pages keep their written tables too, where they say more than a key list can; those can still drift, and the generated ones under them are the reference.
|
||||
|
||||
**Not checked:** the real Fn+h and `?` on the keyboard (the mapper is host-tested; the device was driven with `key help`); the Setup screens, which only a device that was never set up shows, so their new text has not been seen on a screen; and the states that need something to happen first (a dialog, a copy in progress, a Gemini prompt, a packet's details): their lists were read against the key handling, not looked at.
|
||||
|
||||
@@ -71,3 +71,9 @@ On a new device a short Setup asks four things, then never appears again. It als
|
||||
Put a microSD card in the Cardputer. Without one the radio, GNSS, Wi-Fi and IRC still work, but nothing can be saved: no notes, IRC logs, Wi-Fi scan logs, GPX tracks, LoRa captures or saved Gemini pages. The [Storage App](/guide/storage/) shows what is on the card.
|
||||
|
||||
The firmware keeps its own folders at the top of the card (`captures`, `gemini`, `gnss`, `irc`, `updates`, `wifi`, plus `notes`). You can use the card in a computer too, but those names are the firmware's.
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["launcher", "everywhere", "dialog", "text"]) }}
|
||||
|
||||
@@ -40,3 +40,9 @@ Most capsules sign their own certificate. The first certificate seen for a host
|
||||
## Big pages and memory
|
||||
|
||||
With a card, every page streams to the card first, so a page larger than the device's memory still arrives whole and is read from the card as you scroll. Without a card, a page is limited to what fits in memory. If there is not enough free memory to open a secure connection, the fetch says so instead of failing silently; stopping IRC frees the most.
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["gemini", "gemini-saved", "gemini-address", "gemini-answer"]) }}
|
||||
|
||||
@@ -28,3 +28,9 @@ Once there is a fix, the device's clock follows it.
|
||||
## Tracks
|
||||
|
||||
<kbd>r</kbd> starts recording a **Track**: the route is saved as a **GPX** file on the SD card, in `/gnss/tracks` (named by date and time), and the Status Bar shows `REC`. Press <kbd>r</kbd> again to stop. A Track keeps recording with the App closed. It needs a card and a clock (a fix or Wi-Fi sets it); if it cannot start, the App says why: `GNSS is off`, `No SD card` or `Waiting for the time`. The [Storage App](/guide/storage/) opens a `.gpx` file and shows its points, start, duration and distance.
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["gnss"]) }}
|
||||
|
||||
@@ -49,3 +49,9 @@ Every buffer is logged to the SD card, one file per day, under `/irc`. Logs stop
|
||||
|
||||
- IRC **pauses** while Wi-Fi is monitoring (the Status Bar shows `MON`) and while the device **installs an update**. It reconnects and rejoins its channels afterwards; the App says `paused` in the meantime.
|
||||
- A secure connection costs memory: IRC's takes about 40 KB of the 107 KB the device has, and an update's download needs about 52 KB more. That is why IRC steps aside for an update, and why the daily update check waits for IRC to be disconnected (see [Updates](/guide/updates/)).
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["irc", "irc-settings", "irc-field"]) }}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
+++
|
||||
title = "Every key"
|
||||
description = "The keys of every screen of the firmware, as the help panel lists them on the device: one table for each screen and state."
|
||||
weight = 12
|
||||
[extra]
|
||||
tag = "Reference"
|
||||
+++
|
||||
|
||||
On the device, <kbd>Fn</kbd> + <kbd>h</kbd> lists the keys of the screen you are on (and <kbd>?</kbd> does, when you are not typing). This page is all of those lists at once, generated from the same source the firmware reads: `lib/core/src/app_keys.h`.
|
||||
|
||||
In the tables, `; . , /` are the arrow keys (up, down, left, right): alone when you are not typing, with <kbd>Fn</kbd> when you are. `` ` `` is Back, `Aa` is Shift, and two keys separated by spaces are two keys that do the two things listed.
|
||||
|
||||
{{ keys(all=true) }}
|
||||
@@ -33,3 +33,9 @@ A capture records packets into a **pcap** file with LoRaTap headers in `/capture
|
||||
## The GNSS receiver raises the noise
|
||||
|
||||
The GNSS receiver on the same Cap makes the radio's noise floor about **8 dB** worse while it runs. **Settings → Pause GNSS for LoRa** (off by default) puts the receiver on standby while the radio listens, except while a Track is being recorded.
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["lora", "lora-packet", "lora-presets", "lora-sweep"]) }}
|
||||
|
||||
@@ -35,3 +35,9 @@ A new note has no file until you type something. The file is then named after it
|
||||
## Limits
|
||||
|
||||
A note holds up to **16 KB** while it is edited. A bigger text file opens read-only in the [Storage App](/guide/storage/); editing a file of any size is planned. In Storage, <kbd>e</kbd> on a text file opens it in the same editor, anywhere on the card, unless the file is read-only. Notes are never offered for deletion by the clean-up.
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["notes", "notes-editor", "notes-name"]) }}
|
||||
|
||||
@@ -41,3 +41,9 @@ A network normally gives the device its address by itself (DHCP, **Automatic**).
|
||||
### DNS and NTP
|
||||
|
||||
Two DNS servers (9.9.9.9 and 1.1.1.1 by default), used on Fixed networks, or on every network if **Always use my DNS** is on; and two NTP servers (pool.ntp.org and time.cloudflare.com), used after any that the network's DHCP offers. <kbd>Enter</kbd> on **Status** shows what is in use and where each value came from.
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["settings", "settings-choice", "wifi", "wifi-servers", "wifi-network", "wifi-status", "wifi-scan", "wifi-name", "debug-console"]) }}
|
||||
|
||||
@@ -45,3 +45,9 @@ The App says why when it refuses.
|
||||
At the top of the card, the last row, **Maintenance** (or <kbd>m</kbd>), shows the card's usage and holds **Storage clean-up** and **Erase SD card**. They delete for good, so they sit behind a warning. Clean-up deletes old logs and captures by category and age, showing the space it would free first. Notes and Saved Pages are never offered.
|
||||
|
||||
The firmware warns once per start when the card passes **80%** full; past **90%**, logs stop being written so that the rest is kept for captures.
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["storage", "storage-details", "storage-name", "storage-busy", "maintenance", "viewer-text", "viewer-hex", "viewer-pcap", "viewer-packet", "viewer-gpx", "viewer-ota"]) }}
|
||||
|
||||
@@ -18,3 +18,9 @@ System changes nothing: it shows. It samples once a second and keeps its history
|
||||
## Why it exists
|
||||
|
||||
The device has about 107 KB of free memory and no PSRAM, so memory is the resource that decides what can run together: a secure connection takes about 52 KB at its peak. System makes that visible, and is how the project measures its own changes.
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["system", "system-tasks", "system-system"]) }}
|
||||
|
||||
@@ -42,3 +42,9 @@ The connection to the project's server is checked against the two root certifica
|
||||
## For developers
|
||||
|
||||
Updates can also be pushed from a PC over Wi-Fi, with `scripts/flash.sh --ota <ip>`, or put on the card with `scripts/sd_put.sh`: see the [README](https://git.twis.la/twisla/roro9stack#firmware-updates-over-wi-fi-ota). The firmware's console can be reached over Wi-Fi too: see [The Debug Console](/dev/debug/).
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["firmware", "firmware-release", "firmware-older"]) }}
|
||||
|
||||
@@ -34,3 +34,9 @@ One bar for each of the 13 Wi-Fi channels shows how busy it is: how many network
|
||||
## Signal tracker
|
||||
|
||||
Follows one network: its name, address and channel, then the signal in dBm in large type, a strength bar, and a graph of the recent readings, so you can walk towards the strongest signal. <kbd>m</kbd> turns audible clicks on and off. If the network is no longer heard the screen says `lost`. <kbd>`</kbd> (Back) returns to the list.
|
||||
|
||||
## The keys, as the device lists them
|
||||
|
||||
What <kbd>Fn</kbd> + <kbd>h</kbd> shows on these screens. These tables are generated from the firmware's own lists, so they are always the current ones.
|
||||
|
||||
{{ keys(scopes=["wifi-tools", "wifi-networks", "wifi-tracker"]) }}
|
||||
|
||||
@@ -0,0 +1,516 @@
|
||||
# Generated by site/tools/gen_dev_docs.py from lib/core/src/app_keys.h: the keys of every screen, as the
|
||||
# help panel (Fn+h) lists them on the device. Edit that header, not this file.
|
||||
|
||||
[[scope]]
|
||||
id = "everywhere"
|
||||
title = "Everywhere"
|
||||
rows = [
|
||||
["`", "back"],
|
||||
["Fn `", "home, the Launcher"],
|
||||
["; . , /", "arrows (Fn+ while typing)"],
|
||||
["Fn h ?", "these keys (? not typing)"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "dialog"
|
||||
title = "A question"
|
||||
rows = [
|
||||
[", /", "the other answer"],
|
||||
["Enter", "choose it"],
|
||||
["`", "cancel"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "text"
|
||||
title = "A text field"
|
||||
rows = [
|
||||
["Enter", "save"],
|
||||
["`", "cancel"],
|
||||
["Del", "delete backwards"],
|
||||
["Fn , /", "move the cursor"],
|
||||
["opt ' e", "an accent: é"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "launcher"
|
||||
title = "The Launcher"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "open the App"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "setup"
|
||||
title = "Setup, a step"
|
||||
rows = [
|
||||
["Enter", "continue"],
|
||||
["`", "the step before"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "setup-choice"
|
||||
title = "Setup, a choice"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "choose it, next step"],
|
||||
["`", "the step before"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "setup-text"
|
||||
title = "Setup, a name"
|
||||
rows = [
|
||||
["Enter", "next step"],
|
||||
["`", "the step before"],
|
||||
["Del", "delete backwards"],
|
||||
["Fn , /", "move the cursor"],
|
||||
["opt ' e", "an accent: é"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "irc"
|
||||
title = "IRC"
|
||||
rows = [
|
||||
["Enter", "send the line"],
|
||||
["Tab", "the next buffer"],
|
||||
["Alt ; .", "scroll back, forward"],
|
||||
["Fn ; .", "lines you sent before"],
|
||||
["Fn , /", "move the cursor"],
|
||||
["Del", "delete backwards"],
|
||||
["/settings", "server, nick, passwords"],
|
||||
["/join #x", "join a channel"],
|
||||
["/part", "leave it"],
|
||||
["/msg nick", "a private chat"],
|
||||
["/me", "an action"],
|
||||
["/nick", "change your nick"],
|
||||
["/topic", "see or set the topic"],
|
||||
["/names", "who is there"],
|
||||
["/quit", "disconnect, and stay so"],
|
||||
["/raw", "a line as it is"],
|
||||
["`", "leave: IRC stays connected"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "irc-settings"
|
||||
title = "IRC settings"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "edit, switch, or save"],
|
||||
["`", "leave without saving"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "irc-field"
|
||||
title = "IRC, a setting"
|
||||
rows = [
|
||||
["Enter", "keep it"],
|
||||
["`", "cancel"],
|
||||
["Del", "delete backwards"],
|
||||
["Fn , /", "move the cursor"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "wifi-tools"
|
||||
title = "Wi-Fi Tools"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "open"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "wifi-networks"
|
||||
title = "Networks nearby"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "track its signal"],
|
||||
["s", "sort: signal, channel, name"],
|
||||
["o", "open networks only"],
|
||||
["h", "hide the hidden ones"],
|
||||
["w", "strong ones only"],
|
||||
["l", "log the scans to the card"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "wifi-tracker"
|
||||
title = "Signal tracker"
|
||||
rows = [
|
||||
["m", "clicks on or off"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "gnss"
|
||||
title = "GNSS"
|
||||
rows = [
|
||||
["Tab", "the position, or the sky"],
|
||||
["r", "record a Track, or stop it"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "gemini"
|
||||
title = "Gemini, a page"
|
||||
rows = [
|
||||
["Tab", "the next link"],
|
||||
["Aa Tab", "the link before"],
|
||||
["Enter", "follow the link"],
|
||||
["` Del", "the page before"],
|
||||
["; .", "scroll"],
|
||||
["Space", "a page down"],
|
||||
[", /", "sideways, in wide blocks"],
|
||||
["g", "type an address"],
|
||||
["b", "bookmark this page"],
|
||||
["s", "save the page to the card"],
|
||||
["S", "...with the pages it links to"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "gemini-saved"
|
||||
title = "Gemini, a Saved Page"
|
||||
rows = [
|
||||
["Tab", "the next link"],
|
||||
["Aa Tab", "the link before"],
|
||||
["Enter", "follow the link"],
|
||||
["` Del", "the page before"],
|
||||
["; .", "scroll"],
|
||||
["Space", "a page down"],
|
||||
[", /", "sideways, in wide blocks"],
|
||||
["g", "type an address"],
|
||||
["b", "bookmark this page"],
|
||||
["r", "refresh this Saved Page"],
|
||||
["d", "delete this Saved Page"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "gemini-address"
|
||||
title = "Gemini, an address"
|
||||
rows = [
|
||||
["Enter", "go there"],
|
||||
["`", "cancel"],
|
||||
["Del", "delete backwards"],
|
||||
["Fn , /", "move the cursor"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "gemini-answer"
|
||||
title = "Gemini, an answer to a page"
|
||||
rows = [
|
||||
["Enter", "send it"],
|
||||
["`", "cancel"],
|
||||
["Del", "delete backwards"],
|
||||
["Fn , /", "move the cursor"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "lora"
|
||||
title = "LoRa Scanner, the packets"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "the packet's details"],
|
||||
["p", "pick a Meshtastic preset"],
|
||||
["c", "start a Capture, or stop it"],
|
||||
["Tab", "the Sweep"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "lora-packet"
|
||||
title = "LoRa Scanner, a packet"
|
||||
rows = [
|
||||
["; .", "scroll"],
|
||||
["Enter", "back to the list"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "lora-presets"
|
||||
title = "LoRa Scanner, the presets"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "listen with this preset"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "lora-sweep"
|
||||
title = "LoRa Scanner, the Sweep"
|
||||
rows = [
|
||||
["Tab", "the Sniffer"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "storage"
|
||||
title = "Storage, a folder"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "open the folder or the file"],
|
||||
[", /", "a page up, down"],
|
||||
["c x", "copy, cut"],
|
||||
["v", "paste here"],
|
||||
["r", "rename"],
|
||||
["d Del", "delete, after asking"],
|
||||
["n", "a new folder"],
|
||||
["i", "details: size, date, type"],
|
||||
["s", "sort: name, date, size"],
|
||||
["m", "Maintenance: clean-up, erase"],
|
||||
["`", "the folder above"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "storage-details"
|
||||
title = "Storage, an item's details"
|
||||
rows = [
|
||||
["; .", "scroll"],
|
||||
["Enter", "back to the folder"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "storage-name"
|
||||
title = "Storage, a name"
|
||||
rows = [
|
||||
["Enter", "rename it, or make the folder"],
|
||||
["`", "cancel"],
|
||||
["Del", "delete backwards"],
|
||||
["Fn , /", "move the cursor"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "storage-busy"
|
||||
title = "Storage, while it copies or deletes"
|
||||
rows = [
|
||||
["`", "stop the copy or the delete"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "maintenance"
|
||||
title = "Storage, Maintenance"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "open, or choose"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "viewer-text"
|
||||
title = "A file, as text"
|
||||
rows = [
|
||||
["; .", "a line up, down"],
|
||||
[", /", "a page up, down"],
|
||||
["t b", "the top, the end"],
|
||||
["e", "edit it (up to 16 KB)"],
|
||||
["Tab", "the file as hex, or back"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "viewer-hex"
|
||||
title = "A file, as hex"
|
||||
rows = [
|
||||
["; .", "a line up, down"],
|
||||
[", /", "a page up, down"],
|
||||
["t b", "the top, the end"],
|
||||
["Tab", "the file as text, or back"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "viewer-pcap"
|
||||
title = "A Capture"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "the packet"],
|
||||
[", /", "a page up, down"],
|
||||
["Tab", "the file as hex"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "viewer-packet"
|
||||
title = "A Capture's packet"
|
||||
rows = [
|
||||
["; .", "scroll"],
|
||||
["Enter", "back to the packets"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "viewer-gpx"
|
||||
title = "A Track"
|
||||
rows = [
|
||||
["Tab", "the file as text"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "viewer-ota"
|
||||
title = "An Update File"
|
||||
rows = [
|
||||
["Enter", "install it, if it's genuine"],
|
||||
["Tab", "the file as hex"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "notes"
|
||||
title = "Notes, the list"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "open the note"],
|
||||
[", /", "a page up, down"],
|
||||
["n", "a new note"],
|
||||
["r", "rename its file"],
|
||||
["d Del", "delete it"],
|
||||
["s", "sort: newest, or by name"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "notes-editor"
|
||||
title = "Notes, the editor"
|
||||
rows = [
|
||||
["Enter", "a new line"],
|
||||
["Del", "delete backwards"],
|
||||
["Tab", "two spaces"],
|
||||
["Fn ; . , /", "move the cursor"],
|
||||
["Alt Fn ; .", "a page up, down"],
|
||||
["Ctrl a e", "start, end of the line"],
|
||||
["opt ' e", "an accent: é"],
|
||||
["`", "done: it saves by itself"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "notes-name"
|
||||
title = "Notes, a file name"
|
||||
rows = [
|
||||
["Enter", "rename the file"],
|
||||
["`", "cancel"],
|
||||
["Del", "delete backwards"],
|
||||
["Fn , /", "move the cursor"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "system"
|
||||
title = "System, any view"
|
||||
rows = [
|
||||
["Tab", "the next view"],
|
||||
["Aa Tab", "the view before"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "system-tasks"
|
||||
title = "System, the tasks"
|
||||
rows = [
|
||||
["Tab", "the next view"],
|
||||
["Aa Tab", "the view before"],
|
||||
["; .", "scroll"],
|
||||
["s", "sort: cpu, stack, name"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "system-system"
|
||||
title = "System, the system view"
|
||||
rows = [
|
||||
["Tab", "the next view"],
|
||||
["Aa Tab", "the view before"],
|
||||
["; .", "scroll"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "settings"
|
||||
title = "Settings"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "edit, or open the page"],
|
||||
[", /", "change a switch or a slider"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "settings-choice"
|
||||
title = "Settings, a choice"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "choose it"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "wifi"
|
||||
title = "Settings, Wi-Fi"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "open, or change"],
|
||||
[", /", "switch Wi-Fi on or off"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "wifi-servers"
|
||||
title = "Settings, DNS and NTP"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "edit"],
|
||||
[", /", "Always use my DNS: on, off"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "wifi-network"
|
||||
title = "Settings, a saved network"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "edit, or forget"],
|
||||
[", /", "Automatic or Fixed"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "wifi-status"
|
||||
title = "Settings, the Wi-Fi status"
|
||||
rows = [
|
||||
["Enter", "back"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "wifi-scan"
|
||||
title = "Settings, adding a network"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "choose this network"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "wifi-name"
|
||||
title = "Settings, a hidden network's name"
|
||||
rows = [
|
||||
["Enter", "next: the password"],
|
||||
["`", "cancel"],
|
||||
["Del", "delete backwards"],
|
||||
["Fn , /", "move the cursor"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "firmware"
|
||||
title = "Settings, Firmware"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "check, open, or install"],
|
||||
["c", "look for a newer release"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "firmware-release"
|
||||
title = "Settings, a release"
|
||||
rows = [
|
||||
["; .", "scroll"],
|
||||
["Enter", "install it"],
|
||||
["c", "check again"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "firmware-older"
|
||||
title = "Settings, older releases"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "its details"],
|
||||
["c", "read the list again"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "debug-console"
|
||||
title = "Settings, Debug Console"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "switch, or open"],
|
||||
[", /", "switch the console on or off"],
|
||||
]
|
||||
|
||||
[[scope]]
|
||||
id = "demo"
|
||||
title = "The widget demo"
|
||||
rows = [
|
||||
["; .", "up, down"],
|
||||
["Enter", "try the widget"],
|
||||
]
|
||||
@@ -254,3 +254,12 @@ footer small { display: block; max-width: 760px; }
|
||||
.source { max-width: 820px; font-size: 14px; line-height: 20px; }
|
||||
.source code { background: var(--s2); padding: 0 6px; }
|
||||
.card.featured { flex-basis: 100%; }
|
||||
|
||||
/* The keys of a screen, as the device's help panel lists them (the `keys` shortcode) */
|
||||
.keys { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px 32px; }
|
||||
.keys-scope { background: var(--s2); padding: 16px 20px; }
|
||||
.keys-scope h4 { font: 500 12px/16px var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--cyantext); margin: 0 0 8px; }
|
||||
.prose .keys table { width: 100%; }
|
||||
.prose .keys td { padding: 4px 8px 4px 0; border-bottom: 0; font-size: 15px; }
|
||||
.prose .keys td:first-child { white-space: nowrap; width: 1%; padding-right: 16px; }
|
||||
.prose .keys kbd { white-space: pre; }
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{#- The keys of one or more screens, as the help panel (Fn+h) lists them on the device:
|
||||
{{ keys(scopes=["storage", "storage-details"]) }}, or {{ keys(all=true) }} for every screen.
|
||||
The tables come from site/data/keys.toml, generated from lib/core/src/app_keys.h (issue #72):
|
||||
a key added to an App shows up here without anyone editing a page. -#}
|
||||
{%- set data = load_data(path="data/keys.toml", format="toml") -%}
|
||||
{%- set everything = all is defined and all -%}
|
||||
<div class="keys">
|
||||
{%- for s in data.scope %}{% if everything or (scopes is defined and s.id in scopes) %}
|
||||
<div class="keys-scope">
|
||||
<h4 id="keys-{{ s.id }}">{{ s.title }}</h4>
|
||||
<table>
|
||||
{%- for r in s.rows %}
|
||||
<tr><td><kbd>{{ r.0 }}</kbd></td><td>{{ r.1 }}</td></tr>
|
||||
{%- endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{%- endif %}{% endfor %}
|
||||
</div>
|
||||
@@ -9,6 +9,8 @@ Zola cannot read a file outside its own folder, so the pages are generated and c
|
||||
milestones/ one page for each docs/milestones/*.md
|
||||
build/build-and-test, build/flash sections of README.md
|
||||
debug/commands the commands the firmware's `help` prints (parsed from src/main.cpp), then README's table of them
|
||||
site/data/keys.toml every screen's keys, from lib/core/src/app_keys.h: what the help panel (Fn+h) shows on the
|
||||
device, for the `keys` shortcode of the user guide (issue #72)
|
||||
Every generated page says where it comes from; edit that file, not the page. Hand-written pages sit next to them.
|
||||
"""
|
||||
import json
|
||||
@@ -139,8 +141,38 @@ def safe_mode_commands():
|
||||
return exact, prefix
|
||||
|
||||
|
||||
def key_tables():
|
||||
"""Every table of lib/core/src/app_keys.h: [(id, title, [(keys, action), ...])], in the file's order."""
|
||||
src = (REPO / "lib" / "core" / "src" / "app_keys.h").read_text()
|
||||
|
||||
def text(literal): # a C string literal's contents: \xHH bytes are UTF-8
|
||||
raw = re.sub(r"\\x([0-9A-Fa-f]{2})", lambda m: chr(int(m.group(1), 16)), literal).replace('\\"', '"')
|
||||
return raw.encode("latin-1").decode("utf-8")
|
||||
|
||||
tables = []
|
||||
for m in re.finditer(r"// ([a-z0-9-]+): ([^\n]+)\ninline constexpr KeyHelp k\w+\[\] = \{\n(.*?)\n\};", src, re.S):
|
||||
rows = [(text(k), text(a)) for k, a in re.findall(r'\{"((?:[^"\\]|\\.)*)", "((?:[^"\\]|\\.)*)"\},', m.group(3))]
|
||||
if len(rows) != len([l for l in m.group(3).splitlines() if l.strip()]):
|
||||
sys.exit(f"gen_dev_docs: a row of `{m.group(1)}` in app_keys.h isn't in the form {{\"keys\", \"action\"}},")
|
||||
tables.append((m.group(1), m.group(2).strip(), rows))
|
||||
declared = len(re.findall(r"^inline constexpr KeyHelp k\w+\[\]", src, re.M))
|
||||
if len(tables) != declared or len({t[0] for t in tables}) != len(tables):
|
||||
sys.exit(f"gen_dev_docs: app_keys.h has {declared} tables, {len(tables)} with an `// id: Title` comment and a unique id")
|
||||
return tables
|
||||
|
||||
|
||||
def keys_toml():
|
||||
out = ["# Generated by site/tools/gen_dev_docs.py from lib/core/src/app_keys.h: the keys of every screen, as the",
|
||||
"# help panel (Fn+h) lists them on the device. Edit that header, not this file.", ""]
|
||||
for ident, title, rows in key_tables():
|
||||
out += ["[[scope]]", f"id = {json.dumps(ident)}", f"title = {json.dumps(title, ensure_ascii=False)}", "rows = ["]
|
||||
out += [f" [{json.dumps(k, ensure_ascii=False)}, {json.dumps(a, ensure_ascii=False)}]," for k, a in rows]
|
||||
out += ["]", ""]
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def build():
|
||||
pages = {}
|
||||
pages = {"../../data/keys.toml": keys_toml()}
|
||||
|
||||
for path in sorted((REPO / "docs" / "adr").glob("*.md")):
|
||||
title, body = title_and_body(path.read_text())
|
||||
@@ -179,9 +211,25 @@ def build():
|
||||
return pages
|
||||
|
||||
|
||||
def unknown_scopes():
|
||||
"""Scopes a page asks the `keys` shortcode for that app_keys.h doesn't have."""
|
||||
known = {t[0] for t in key_tables()}
|
||||
bad = []
|
||||
for path in sorted((SITE / "content").rglob("*.md")):
|
||||
for call in re.findall(r"keys\(scopes=\[([^\]]*)\]", path.read_text()):
|
||||
for ident in re.findall(r'"([^"]+)"', call):
|
||||
if ident not in known:
|
||||
bad.append(f"{path.relative_to(SITE)}: no key table `{ident}` in lib/core/src/app_keys.h")
|
||||
return bad
|
||||
|
||||
|
||||
def main():
|
||||
check = "--check" in sys.argv
|
||||
pages = build()
|
||||
for problem in unknown_scopes():
|
||||
print("gen_dev_docs:", problem)
|
||||
if unknown_scopes():
|
||||
sys.exit(1)
|
||||
stale = []
|
||||
for rel, text in sorted(pages.items()):
|
||||
path = OUT / rel
|
||||
@@ -193,10 +241,10 @@ def main():
|
||||
path.write_text(text)
|
||||
if check:
|
||||
for rel in stale:
|
||||
print(f"gen_dev_docs: content/dev/{rel} is out of date: run site/tools/gen_dev_docs.py and commit the result")
|
||||
print(f"gen_dev_docs: {len(pages)} pages, {len(stale)} out of date")
|
||||
print(f"gen_dev_docs: {os.path.normpath(os.path.join('site/content/dev', rel))} is out of date: run site/tools/gen_dev_docs.py and commit the result")
|
||||
print(f"gen_dev_docs: {len(pages)} files, {len(stale)} out of date")
|
||||
sys.exit(1 if stale else 0)
|
||||
print(f"gen_dev_docs: {len(pages)} pages, {len(stale)} written")
|
||||
print(f"gen_dev_docs: {len(pages)} files, {len(stale)} written")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "apps/debug_console_page.h"
|
||||
|
||||
#include "debug_auth.h"
|
||||
@@ -75,10 +76,9 @@ bool DebugConsolePage::onKey(const KeyEvent& e) {
|
||||
}
|
||||
|
||||
void DebugConsolePage::help(std::vector<KeyHelp>& out) const {
|
||||
if (confirm_) return help::dialog(out);
|
||||
if (typing_) return help::textEntry(out);
|
||||
help::list(out, "switch, or open");
|
||||
out.push_back({", /", "switch the console on or off"});
|
||||
if (confirm_) return keys::add(out, keys::kDialog);
|
||||
if (typing_) return keys::add(out, keys::kText);
|
||||
keys::add(out, keys::kDebugConsole);
|
||||
}
|
||||
|
||||
void DebugConsolePage::draw(Canvas& c) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "demo_app.h"
|
||||
|
||||
#include "ui/widgets.h"
|
||||
@@ -26,12 +27,9 @@ void DemoApp::notify(const char* text, NotificationLevel level) {
|
||||
}
|
||||
|
||||
void DemoApp::help(std::vector<KeyHelp>& out) const {
|
||||
if (dialog_) return help::dialog(out);
|
||||
switch (page_) {
|
||||
case Page::Menu: help::list(out, "try the widget"); break;
|
||||
case Page::Text: out.push_back({"; .", "scroll"}); break;
|
||||
case Page::Editor: help::textEntry(out, "show the text as a Toast"); break;
|
||||
}
|
||||
if (dialog_) return keys::add(out, keys::kDialog);
|
||||
if (page_ == Page::Editor) return keys::add(out, keys::kText);
|
||||
if (page_ == Page::Menu) keys::add(out, keys::kDemo);
|
||||
}
|
||||
|
||||
bool DemoApp::onKey(const KeyEvent& e) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "file_viewer.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -266,27 +267,15 @@ void FileViewer::openPacket(int index) {
|
||||
}
|
||||
|
||||
void FileViewer::help(std::vector<KeyHelp>& out) const {
|
||||
if (confirm_) return help::dialog(out);
|
||||
if (confirm_) return keys::add(out, keys::kDialog);
|
||||
switch (mode_) {
|
||||
case Mode::Packet:
|
||||
out.push_back({"; .", "scroll"});
|
||||
out.push_back({"Enter", "back to the packets"});
|
||||
return;
|
||||
case Mode::Text:
|
||||
case Mode::Hex:
|
||||
out.push_back({"; .", "a line up, down"});
|
||||
out.push_back({", /", "a page up, down"});
|
||||
out.push_back({"t b", "the top, the end"});
|
||||
if (mode_ == Mode::Text) out.push_back({"e", "edit it (up to 16 KB)"});
|
||||
break;
|
||||
case Mode::Pcap:
|
||||
help::list(out, "the packet");
|
||||
out.push_back({", /", "a page up, down"});
|
||||
break;
|
||||
case Mode::Ota: out.push_back({"Enter", "install it, if it's genuine"}); break;
|
||||
case Mode::Gpx: break;
|
||||
case Mode::Text: keys::add(out, keys::kViewerText); break;
|
||||
case Mode::Hex: keys::add(out, keys::kViewerHex); break;
|
||||
case Mode::Pcap: keys::add(out, keys::kViewerPcap); break;
|
||||
case Mode::Packet: keys::add(out, keys::kViewerPacket); break;
|
||||
case Mode::Gpx: keys::add(out, keys::kViewerGpx); break;
|
||||
case Mode::Ota: keys::add(out, keys::kViewerOta); break;
|
||||
}
|
||||
out.push_back({"Tab", mode_ != base_ ? "back to the file's own view" : base_ == Mode::Hex || base_ == Mode::Gpx ? "the file as text" : "the file as hex"});
|
||||
}
|
||||
|
||||
bool FileViewer::onKey(const KeyEvent& e) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "firmware_page.h"
|
||||
|
||||
#include <SD.h>
|
||||
@@ -235,21 +236,11 @@ void FirmwarePage::drawOlder(Canvas& c) {
|
||||
}
|
||||
|
||||
void FirmwarePage::help(std::vector<KeyHelp>& out) const {
|
||||
if (confirm_) return help::dialog(out);
|
||||
if (confirm_) return keys::add(out, keys::kDialog);
|
||||
switch (view_) {
|
||||
case View::Main:
|
||||
help::list(out, "check, open, or install");
|
||||
out.push_back({"c", "look for a newer release"});
|
||||
break;
|
||||
case View::Release:
|
||||
out.push_back({"; .", "scroll"});
|
||||
out.push_back({"Enter", "install it"});
|
||||
out.push_back({"c", "check again"});
|
||||
break;
|
||||
case View::Older:
|
||||
help::list(out, "its details");
|
||||
out.push_back({"c", "read the list again"});
|
||||
break;
|
||||
case View::Main: keys::add(out, keys::kFirmware); break;
|
||||
case View::Release: keys::add(out, keys::kFirmwareRelease); break;
|
||||
case View::Older: keys::add(out, keys::kFirmwareOlder); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-19
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "gemini_app.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -303,25 +304,11 @@ void GeminiApp::selectLink(int direction) {
|
||||
}
|
||||
|
||||
void GeminiApp::help(std::vector<KeyHelp>& out) const {
|
||||
if (certDialog_ || deleteDialog_) return help::dialog(out);
|
||||
if (inputOpen_) return help::textEntry(out, "send it");
|
||||
if (addressOpen_) return help::textEntry(out, "go there");
|
||||
out.push_back({"Tab", "the next link"});
|
||||
out.push_back({"Aa Tab", "the link before"});
|
||||
out.push_back({"Enter", "follow the link"});
|
||||
out.push_back({"` Del", "the page before"});
|
||||
out.push_back({"; .", "scroll"});
|
||||
out.push_back({"Space", "a page down"});
|
||||
out.push_back({", /", "sideways, in wide blocks"});
|
||||
out.push_back({"g", "type an address"});
|
||||
out.push_back({"b", "bookmark this page"});
|
||||
if (isSaved()) {
|
||||
out.push_back({"r", "refresh this Saved Page"});
|
||||
out.push_back({"d", "delete this Saved Page"});
|
||||
} else {
|
||||
out.push_back({"s", "save the page to the card"});
|
||||
out.push_back({"S", "...with the pages it links to"});
|
||||
}
|
||||
if (certDialog_ || deleteDialog_) return keys::add(out, keys::kDialog);
|
||||
if (inputOpen_) return keys::add(out, keys::kGeminiAnswer);
|
||||
if (addressOpen_) return keys::add(out, keys::kGeminiAddress);
|
||||
if (isSaved()) keys::add(out, keys::kGeminiSaved);
|
||||
else keys::add(out, keys::kGemini);
|
||||
}
|
||||
|
||||
const char* GeminiApp::helpTitle() const {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "gnss_app.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -83,8 +84,7 @@ void GnssApp::draw(Canvas& c) {
|
||||
}
|
||||
|
||||
void GnssApp::help(std::vector<KeyHelp>& out) const {
|
||||
out.push_back({"Tab", sky_ ? "the position" : "the sky"});
|
||||
out.push_back({"r", gnss_.tracking() ? "stop the Track" : "record a Track"});
|
||||
keys::add(out, keys::kGnss);
|
||||
}
|
||||
|
||||
void GnssApp::drawPosition(Canvas& c) {
|
||||
|
||||
+4
-23
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "irc_app.h"
|
||||
|
||||
#include "ui/fonts.h"
|
||||
@@ -300,29 +301,9 @@ void IrcApp::drawChat(Canvas& c) {
|
||||
}
|
||||
|
||||
void IrcApp::help(std::vector<KeyHelp>& out) const {
|
||||
if (page_ == Page::Settings) {
|
||||
if (editing_) return help::textEntry(out, "keep it");
|
||||
help::list(out, "edit, switch, or save");
|
||||
out.push_back({"`", "leave without saving"});
|
||||
return;
|
||||
}
|
||||
out.push_back({"Enter", "send the line"});
|
||||
out.push_back({"Tab", "the next buffer"});
|
||||
out.push_back({"Alt ; .", "scroll back, forward"});
|
||||
out.push_back({"Fn ; .", "lines you sent before"});
|
||||
out.push_back({"Fn , /", "move the cursor"});
|
||||
out.push_back({"Del", "delete backwards"});
|
||||
out.push_back({"/settings", "server, nick, passwords"});
|
||||
out.push_back({"/join #x", "join a channel"});
|
||||
out.push_back({"/part", "leave it"});
|
||||
out.push_back({"/msg nick", "a private chat"});
|
||||
out.push_back({"/me", "an action"});
|
||||
out.push_back({"/nick", "change your nick"});
|
||||
out.push_back({"/topic", "see or set the topic"});
|
||||
out.push_back({"/names", "who is there"});
|
||||
out.push_back({"/quit", "disconnect, and stay so"});
|
||||
out.push_back({"/raw", "a line as it is"});
|
||||
out.push_back({"`", "leave: IRC stays connected"});
|
||||
if (page_ == Page::Chat) return keys::add(out, keys::kIrc);
|
||||
if (editing_) return keys::add(out, keys::kIrcField);
|
||||
keys::add(out, keys::kIrcSettings);
|
||||
}
|
||||
|
||||
const char* IrcApp::helpTitle() const {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "app.h"
|
||||
#include "app_keys.h"
|
||||
#include "app_manager.h"
|
||||
#include "list_model.h"
|
||||
#include "ui/theme.h"
|
||||
@@ -14,7 +15,7 @@ class LauncherApp : public App {
|
||||
void onEnter() override;
|
||||
bool onKey(const KeyEvent& e) override;
|
||||
void draw(Canvas& c) override;
|
||||
void help(std::vector<KeyHelp>& out) const override { help::list(out, "open the App"); }
|
||||
void help(std::vector<KeyHelp>& out) const override { keys::add(out, keys::kLauncher); }
|
||||
|
||||
private:
|
||||
AppManager* manager_ = nullptr;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "lora_scanner_app.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -167,18 +168,10 @@ void LoraScannerApp::update(uint32_t nowMs) {
|
||||
|
||||
void LoraScannerApp::help(std::vector<KeyHelp>& out) const {
|
||||
switch (view_) {
|
||||
case View::Packets:
|
||||
help::list(out, "the packet's details");
|
||||
out.push_back({"p", "pick a Meshtastic preset"});
|
||||
out.push_back({"c", capture_.capturing() ? "stop the Capture" : "start a Capture (pcap)"});
|
||||
out.push_back({"Tab", "the Sweep"});
|
||||
break;
|
||||
case View::Details:
|
||||
out.push_back({"; .", "scroll"});
|
||||
out.push_back({"Enter", "back to the list"});
|
||||
break;
|
||||
case View::Presets: help::list(out, "listen with this preset"); break;
|
||||
case View::Sweep: out.push_back({"Tab", "the Sniffer"}); break;
|
||||
case View::Packets: keys::add(out, keys::kLora); break;
|
||||
case View::Details: keys::add(out, keys::kLoraPacket); break;
|
||||
case View::Presets: keys::add(out, keys::kLoraPresets); break;
|
||||
case View::Sweep: keys::add(out, keys::kLoraSweep); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "maintenance_page.h"
|
||||
|
||||
#include "ui/widgets.h"
|
||||
@@ -36,8 +37,8 @@ CleanupPlan MaintenancePage::planFor(int age) const {
|
||||
}
|
||||
|
||||
void MaintenancePage::help(std::vector<KeyHelp>& out) const {
|
||||
if (dialog_) return help::dialog(out);
|
||||
help::list(out, view_ == View::Main ? "open" : view_ == View::Categories ? "choose what to clean" : "choose how old");
|
||||
if (dialog_) return keys::add(out, keys::kDialog);
|
||||
keys::add(out, keys::kMaintenance);
|
||||
}
|
||||
|
||||
bool MaintenancePage::onKey(const KeyEvent& e) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "note_editor.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -183,15 +184,8 @@ bool NoteEditor::save() {
|
||||
}
|
||||
|
||||
void NoteEditor::help(std::vector<KeyHelp>& out) const {
|
||||
if (dialog_) return help::dialog(out);
|
||||
out.push_back({"Enter", "a new line"});
|
||||
out.push_back({"Del", "delete backwards"});
|
||||
out.push_back({"Tab", "two spaces"});
|
||||
out.push_back({"Fn ; . , /", "move the cursor"});
|
||||
out.push_back({"Alt Fn ; .", "a page up, down"});
|
||||
out.push_back({"Ctrl a e", "start, end of the line"});
|
||||
out.push_back({"opt ' e", "an accent: \xC3\xA9"});
|
||||
out.push_back({"`", "done: it saves by itself"});
|
||||
if (dialog_) return keys::add(out, keys::kDialog);
|
||||
keys::add(out, keys::kNotesEditor);
|
||||
}
|
||||
|
||||
bool NoteEditor::onKey(const KeyEvent& e) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "notes_app.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -175,15 +176,10 @@ std::string NotesApp::titleOf(int row) {
|
||||
|
||||
void NotesApp::help(std::vector<KeyHelp>& out) const {
|
||||
if (view_ == View::Edit) return editor_.help(out);
|
||||
if (dialog_) return help::dialog(out);
|
||||
if (view_ == View::Name) return help::textEntry(out, "rename the file");
|
||||
if (dialog_) return keys::add(out, keys::kDialog);
|
||||
if (view_ == View::Name) return keys::add(out, keys::kNotesName);
|
||||
if (view_ == View::NoMemory) return;
|
||||
help::list(out, "open the note");
|
||||
out.push_back({", /", "a page up, down"});
|
||||
out.push_back({"n", "a new note"});
|
||||
out.push_back({"r", "rename its file"});
|
||||
out.push_back({"d Del", "delete it"});
|
||||
out.push_back({"s", "sort: newest, or by name"});
|
||||
keys::add(out, keys::kNotes);
|
||||
}
|
||||
|
||||
const char* NotesApp::helpTitle() const {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "settings_app.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -131,12 +132,9 @@ bool SettingsApp::onAboutKey(const KeyEvent& e) {
|
||||
|
||||
void SettingsApp::help(std::vector<KeyHelp>& out) const {
|
||||
switch (page_) {
|
||||
case Page::Menu:
|
||||
help::list(out, "edit, or open the page");
|
||||
out.push_back({", /", "change a switch or a slider"});
|
||||
break;
|
||||
case Page::Text: help::textEntry(out); break;
|
||||
case Page::Choice: help::list(out, "choose it"); break;
|
||||
case Page::Menu: keys::add(out, keys::kSettings); break;
|
||||
case Page::Text: keys::add(out, keys::kText); break;
|
||||
case Page::Choice: keys::add(out, keys::kSettingsChoice); break;
|
||||
case Page::About: break;
|
||||
case Page::Wifi: wifiPage_.help(out); break;
|
||||
case Page::Firmware: firmwarePage_.help(out); break;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "setup_app.h"
|
||||
|
||||
#include "platform/identity.h"
|
||||
@@ -47,16 +48,10 @@ bool SetupApp::onKey(const KeyEvent& e) {
|
||||
void SetupApp::help(std::vector<KeyHelp>& out) const {
|
||||
switch (wizard_.step()) {
|
||||
case Step::LongName:
|
||||
case Step::ShortName: help::textEntry(out, "next step"); break;
|
||||
case Step::ShortName: keys::add(out, keys::kSetupText); break;
|
||||
case Step::Region:
|
||||
case Step::Timezone:
|
||||
help::list(out, "choose it, next step");
|
||||
out.push_back({"`", "the step before"});
|
||||
break;
|
||||
default:
|
||||
out.push_back({"Enter", "continue"});
|
||||
out.push_back({"`", "the step before"});
|
||||
break;
|
||||
case Step::Timezone: keys::add(out, keys::kSetupChoice); break;
|
||||
default: keys::add(out, keys::kSetup); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "storage_app.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -279,26 +280,13 @@ void StorageApp::help(std::vector<KeyHelp>& out) const {
|
||||
case View::Maintenance: return maintenance_.help(out);
|
||||
case View::Editor: return noteEditor_.help(out);
|
||||
case View::Viewer: return viewer_.help(out);
|
||||
case View::Details:
|
||||
out.push_back({"; .", "scroll"});
|
||||
out.push_back({"Enter", "back to the folder"});
|
||||
return;
|
||||
case View::Details: return keys::add(out, keys::kStorageDetails);
|
||||
default: break;
|
||||
}
|
||||
if (dialog_) return help::dialog(out);
|
||||
if (wantList_ || wait_ != Wait::None) return (void)out.push_back({"`", "stop the copy or the delete"});
|
||||
if (view_ == View::Name) return help::textEntry(out, renaming_ ? "rename it" : "make the folder");
|
||||
help::list(out, "open the folder or the file");
|
||||
out.push_back({", /", "a page up, down"});
|
||||
out.push_back({"c x", "copy, cut"});
|
||||
out.push_back({"v", "paste here"});
|
||||
out.push_back({"r", "rename"});
|
||||
out.push_back({"d Del", "delete, after asking"});
|
||||
out.push_back({"n", "a new folder"});
|
||||
out.push_back({"i", "details: size, date, type"});
|
||||
out.push_back({"s", "sort: name, date, size"});
|
||||
out.push_back({"m", "Maintenance: clean-up, erase"});
|
||||
out.push_back({"`", "the folder above"});
|
||||
if (dialog_) return keys::add(out, keys::kDialog);
|
||||
if (wantList_ || wait_ != Wait::None) return keys::add(out, keys::kStorageBusy);
|
||||
if (view_ == View::Name) return keys::add(out, keys::kStorageName);
|
||||
keys::add(out, keys::kStorage);
|
||||
}
|
||||
|
||||
const char* StorageApp::helpTitle() const {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "system_app.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -74,13 +75,9 @@ void SystemApp::onExit() { // nothing is kept while it's closed (Q120)
|
||||
}
|
||||
|
||||
void SystemApp::help(std::vector<KeyHelp>& out) const {
|
||||
out.push_back({"Tab", "the next view"});
|
||||
out.push_back({"Aa Tab", "the view before"});
|
||||
if (view_ == View::Tasks) {
|
||||
out.push_back({"; .", "scroll"});
|
||||
out.push_back({"s", "sort: cpu, stack, name"});
|
||||
}
|
||||
if (view_ == View::System) out.push_back({"; .", "scroll"});
|
||||
if (view_ == View::Tasks) keys::add(out, keys::kSystemTasks);
|
||||
else if (view_ == View::System) keys::add(out, keys::kSystemSystem);
|
||||
else keys::add(out, keys::kSystem);
|
||||
}
|
||||
|
||||
const char* SystemApp::helpTitle() const {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "wifi_settings_page.h"
|
||||
|
||||
#include "ipv4.h"
|
||||
@@ -449,25 +450,16 @@ void WifiSettingsPage::draw(Canvas& c) {
|
||||
}
|
||||
|
||||
void WifiSettingsPage::help(std::vector<KeyHelp>& out) const {
|
||||
if (forgetDialog_) return help::dialog(out);
|
||||
if (forgetDialog_) return keys::add(out, keys::kDialog);
|
||||
switch (view_) {
|
||||
case View::Main:
|
||||
help::list(out, "open, or change");
|
||||
out.push_back({", /", "switch Wi-Fi on or off"});
|
||||
break;
|
||||
case View::Servers:
|
||||
help::list(out, "edit");
|
||||
out.push_back({", /", "Always use my DNS: on, off"});
|
||||
break;
|
||||
case View::Network:
|
||||
help::list(out, "edit, or forget");
|
||||
out.push_back({", /", "Automatic or Fixed"});
|
||||
break;
|
||||
case View::Details: out.push_back({"Enter", "back"}); break;
|
||||
case View::Scan: help::list(out, "choose this network"); break;
|
||||
case View::Ssid: help::textEntry(out, "next: the password"); break;
|
||||
case View::Main: keys::add(out, keys::kWifi); break;
|
||||
case View::Servers: keys::add(out, keys::kWifiServers); break;
|
||||
case View::Network: keys::add(out, keys::kWifiNetwork); break;
|
||||
case View::Details: keys::add(out, keys::kWifiStatus); break;
|
||||
case View::Scan: keys::add(out, keys::kWifiScan); break;
|
||||
case View::Ssid: keys::add(out, keys::kWifiName); break;
|
||||
case View::Password:
|
||||
case View::Edit: help::textEntry(out); break;
|
||||
case View::Edit: keys::add(out, keys::kText); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "app_keys.h"
|
||||
#include "wifi_tools_app.h"
|
||||
|
||||
#include <M5Cardputer.h>
|
||||
@@ -155,17 +156,10 @@ void WifiToolsApp::draw(Canvas& c) {
|
||||
|
||||
void WifiToolsApp::help(std::vector<KeyHelp>& out) const {
|
||||
switch (view_) {
|
||||
case View::Menu: help::list(out); break;
|
||||
case View::Networks:
|
||||
help::list(out, "track its signal");
|
||||
out.push_back({"s", "sort: signal, channel, name"});
|
||||
out.push_back({"o", "open networks only"});
|
||||
out.push_back({"h", "hide the hidden ones"});
|
||||
out.push_back({"w", "strong ones only"});
|
||||
out.push_back({"l", "log the scans to the card"});
|
||||
break;
|
||||
case View::Channels: break;
|
||||
case View::Tracker: out.push_back({"m", "clicks on or off"}); break;
|
||||
case View::Menu: keys::add(out, keys::kWifiTools); break;
|
||||
case View::Networks: keys::add(out, keys::kWifiNetworks); break;
|
||||
case View::Channels: break; // nothing but the keys that work everywhere
|
||||
case View::Tracker: keys::add(out, keys::kWifiTracker); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user