mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-15 23:32:34 +00:00
Compare commits
15 Commits
master
...
InkHUD-Imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96e82f1ec1 | ||
|
|
83ec37113d | ||
|
|
5acf72243d | ||
|
|
bd18a171d4 | ||
|
|
6e05c554b8 | ||
|
|
5f9a6a38e6 | ||
|
|
a332ca978b | ||
|
|
7b4315421b | ||
|
|
60389e84e6 | ||
|
|
cd0843c7db | ||
|
|
d9dab0cd6c | ||
|
|
d0d375f1ff | ||
|
|
e8ebfc0513 | ||
|
|
87114f4052 | ||
|
|
bf32f17f28 |
2
.github/actions/build-variant/action.yml
vendored
2
.github/actions/build-variant/action.yml
vendored
@@ -100,7 +100,7 @@ runs:
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}
|
||||
overwrite: true
|
||||
|
||||
2
.github/workflows/build_debian_src.yml
vendored
2
.github/workflows/build_debian_src.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
PKG_VERSION: ${{ steps.version.outputs.deb }}
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
overwrite: true
|
||||
|
||||
10
.github/workflows/build_firmware.yml
vendored
10
.github/workflows/build_firmware.yml
vendored
@@ -56,19 +56,21 @@ jobs:
|
||||
ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }}
|
||||
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
|
||||
|
||||
- name: Echo manifest from release/firmware-*.mt.json to job summary
|
||||
if: ${{ always() }}
|
||||
- name: Job summary
|
||||
env:
|
||||
PIO_ENV: ${{ inputs.pio_env }}
|
||||
run: |
|
||||
echo "## Manifest: \`$PIO_ENV\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## $PIO_ENV" >> $GITHUB_STEP_SUMMARY
|
||||
echo "<details><summary><strong>Manifest</strong></summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo '' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
cat release/firmware-*.mt.json >> $GITHUB_STEP_SUMMARY
|
||||
echo '' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
id: upload
|
||||
with:
|
||||
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
|
||||
|
||||
8
.github/workflows/build_one_target.yml
vendored
8
.github/workflows/build_one_target.yml
vendored
@@ -98,7 +98,7 @@ jobs:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: ./
|
||||
pattern: firmware-*-*
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-*-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
|
||||
22
.github/workflows/main_matrix.yml
vendored
22
.github/workflows/main_matrix.yml
vendored
@@ -173,7 +173,7 @@ jobs:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: ./
|
||||
pattern: firmware-${{matrix.arch}}-*
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
run: ls -R
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -200,7 +200,7 @@ jobs:
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -219,7 +219,7 @@ jobs:
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -260,14 +260,14 @@ jobs:
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||
merge-multiple: true
|
||||
path: ./output/debian-src
|
||||
|
||||
- name: Download `native-tft` pio deps
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -291,7 +291,7 @@ jobs:
|
||||
}' > firmware-${{ needs.version.outputs.long }}.json
|
||||
|
||||
- name: Save Release manifest artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: manifest-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -332,7 +332,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -349,7 +349,7 @@ jobs:
|
||||
- name: Zip firmware
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -388,14 +388,14 @@ jobs:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Get firmware artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./publish
|
||||
|
||||
- name: Get manifest artifact
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: manifest-${{ needs.version.outputs.long }}
|
||||
path: ./publish
|
||||
|
||||
18
.github/workflows/merge_queue.yml
vendored
18
.github/workflows/merge_queue.yml
vendored
@@ -147,7 +147,7 @@ jobs:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: ./
|
||||
pattern: firmware-${{matrix.arch}}-*
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -235,14 +235,14 @@ jobs:
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||
merge-multiple: true
|
||||
path: ./output/debian-src
|
||||
|
||||
- name: Download `native-tft` pio deps
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -292,7 +292,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -309,7 +309,7 @@ jobs:
|
||||
- name: Zip firmware
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -347,7 +347,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/package_obs.yml
vendored
2
.github/workflows/package_obs.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/package_pio_deps.yml
vendored
2
.github/workflows/package_pio_deps.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
PLATFORMIO_CORE_DIR: pio/core
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
|
||||
overwrite: true
|
||||
|
||||
2
.github/workflows/package_ppa.yml
vendored
2
.github/workflows/package_ppa.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/pr_tests.yml
vendored
2
.github/workflows/pr_tests.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Download test artifacts
|
||||
if: needs.native-tests.result != 'skipped'
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/sec_sast_semgrep_cron.yml
vendored
2
.github/workflows/sec_sast_semgrep_cron.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
# step 3
|
||||
- name: save report as pipeline artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: report.sarif
|
||||
overwrite: true
|
||||
|
||||
12
.github/workflows/test_native.yml
vendored
12
.github/workflows/test_native.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Save coverage information
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
if: always() # run this step even if previous step failed
|
||||
with:
|
||||
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
|
||||
- name: Save test results
|
||||
if: always() # run this step even if previous step failed
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative.
|
||||
|
||||
- name: Save coverage information
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
if: always() # run this step even if previous step failed
|
||||
with:
|
||||
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download test artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
reporter: java-junit
|
||||
|
||||
- name: Download coverage artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}
|
||||
path: code-coverage-report
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report
|
||||
|
||||
- name: Save Code Coverage Report
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: code-coverage-report-${{ steps.version.outputs.long }}
|
||||
path: code-coverage-report
|
||||
|
||||
@@ -9,7 +9,7 @@ plugins:
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.495
|
||||
- renovate@42.52.8
|
||||
- renovate@42.48.0
|
||||
- prettier@3.7.4
|
||||
- trufflehog@3.92.3
|
||||
- yamllint@1.37.1
|
||||
|
||||
@@ -123,7 +123,7 @@ lib_deps =
|
||||
[device-ui_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||
https://github.com/meshtastic/device-ui/archive/862ed040c4ab44f0dfbbe492691f144886102588.zip
|
||||
https://github.com/meshtastic/device-ui/archive/2746a1ce3804998460a2cb319b8ea8a238dfd8c9.zip
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
[environmental_base]
|
||||
|
||||
Submodule protobufs updated: 1cf2783bdb...4095e59890
@@ -22,6 +22,7 @@ enum MenuAction {
|
||||
STORE_CANNEDMESSAGE_SELECTION,
|
||||
SEND_CANNEDMESSAGE,
|
||||
SHUTDOWN,
|
||||
BACK,
|
||||
NEXT_TILE,
|
||||
TOGGLE_BACKLIGHT,
|
||||
TOGGLE_GPS,
|
||||
@@ -35,6 +36,77 @@ enum MenuAction {
|
||||
TOGGLE_NOTIFICATIONS,
|
||||
TOGGLE_INVERT_COLOR,
|
||||
TOGGLE_12H_CLOCK,
|
||||
// Regions
|
||||
SET_REGION_US,
|
||||
SET_REGION_EU_868,
|
||||
SET_REGION_EU_433,
|
||||
SET_REGION_CN,
|
||||
SET_REGION_JP,
|
||||
SET_REGION_ANZ,
|
||||
SET_REGION_KR,
|
||||
SET_REGION_TW,
|
||||
SET_REGION_RU,
|
||||
SET_REGION_IN,
|
||||
SET_REGION_NZ_865,
|
||||
SET_REGION_TH,
|
||||
SET_REGION_LORA_24,
|
||||
SET_REGION_UA_433,
|
||||
SET_REGION_UA_868,
|
||||
SET_REGION_MY_433,
|
||||
SET_REGION_MY_919,
|
||||
SET_REGION_SG_923,
|
||||
SET_REGION_PH_433,
|
||||
SET_REGION_PH_868,
|
||||
SET_REGION_PH_915,
|
||||
SET_REGION_ANZ_433,
|
||||
SET_REGION_KZ_433,
|
||||
SET_REGION_KZ_863,
|
||||
SET_REGION_NP_865,
|
||||
SET_REGION_BR_902,
|
||||
// Device Roles
|
||||
SET_ROLE_CLIENT,
|
||||
SET_ROLE_CLIENT_MUTE,
|
||||
SET_ROLE_ROUTER,
|
||||
SET_ROLE_REPEATER,
|
||||
// Presets
|
||||
SET_PRESET_LONG_SLOW,
|
||||
SET_PRESET_LONG_MODERATE,
|
||||
SET_PRESET_LONG_FAST,
|
||||
SET_PRESET_MEDIUM_SLOW,
|
||||
SET_PRESET_MEDIUM_FAST,
|
||||
SET_PRESET_SHORT_SLOW,
|
||||
SET_PRESET_SHORT_FAST,
|
||||
SET_PRESET_SHORT_TURBO,
|
||||
// Timezones
|
||||
SET_TZ_US_HAWAII,
|
||||
SET_TZ_US_ALASKA,
|
||||
SET_TZ_US_PACIFIC,
|
||||
SET_TZ_US_ARIZONA,
|
||||
SET_TZ_US_MOUNTAIN,
|
||||
SET_TZ_US_CENTRAL,
|
||||
SET_TZ_US_EASTERN,
|
||||
SET_TZ_BR_BRAZILIA,
|
||||
SET_TZ_UTC,
|
||||
SET_TZ_EU_WESTERN,
|
||||
SET_TZ_EU_CENTRAL,
|
||||
SET_TZ_EU_EASTERN,
|
||||
SET_TZ_ASIA_KOLKATA,
|
||||
SET_TZ_ASIA_HONG_KONG,
|
||||
SET_TZ_AU_AWST,
|
||||
SET_TZ_AU_ACST,
|
||||
SET_TZ_AU_AEST,
|
||||
SET_TZ_PACIFIC_NZ,
|
||||
// Power
|
||||
TOGGLE_POWER_SAVE,
|
||||
// Bluetooth
|
||||
TOGGLE_BLUETOOTH,
|
||||
TOGGLE_BLUETOOTH_PAIR_MODE,
|
||||
// Channel
|
||||
TOGGLE_CHANNEL_UPLINK,
|
||||
TOGGLE_CHANNEL_DOWNLINK,
|
||||
TOGGLE_CHANNEL_POSITION,
|
||||
SET_CHANNEL_PRECISION,
|
||||
TOGGLE_DISPLAY_UNITS,
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
@@ -4,11 +4,15 @@
|
||||
|
||||
#include "RTC.h"
|
||||
|
||||
#include "DisplayFormatters.h"
|
||||
#include "MeshService.h"
|
||||
#include "Router.h"
|
||||
#include "airtime.h"
|
||||
#include "main.h"
|
||||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||
#include "power.h"
|
||||
#include <RadioLibInterface.h>
|
||||
#include <target_specific.h>
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "GPS.h"
|
||||
@@ -22,6 +26,18 @@ static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu
|
||||
// These are offered to users as possible values for settings.recentlyActiveSeconds
|
||||
static constexpr uint8_t RECENTS_OPTIONS_MINUTES[] = {2, 5, 10, 30, 60, 120};
|
||||
|
||||
struct PositionPrecisionOption {
|
||||
uint8_t value; // proto value
|
||||
const char *metric;
|
||||
const char *imperial;
|
||||
};
|
||||
|
||||
static constexpr PositionPrecisionOption POSITION_PRECISION_OPTIONS[] = {
|
||||
{32, "Precise", "Precise"}, {19, "50 m", "150 ft"}, {18, "90 m", "300 ft"}, {17, "200 m", "600 ft"},
|
||||
{16, "350 m", "0.2 mi"}, {15, "700 m", "0.5 mi"}, {14, "1.5 km", "0.9 mi"}, {13, "2.9 km", "1.8 mi"},
|
||||
{12, "5.8 km", "3.6 mi"}, {11, "12 km", "7.3 mi"}, {10, "23 km", "15 mi"},
|
||||
};
|
||||
|
||||
InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet")
|
||||
{
|
||||
// No timer tasks at boot
|
||||
@@ -45,8 +61,15 @@ void InkHUD::MenuApplet::onForeground()
|
||||
// We do need this before we render, but we can optimize by just calculating it once now
|
||||
systemInfoPanelHeight = getSystemInfoPanelHeight();
|
||||
|
||||
// Display initial menu page
|
||||
showPage(MenuPage::ROOT);
|
||||
// Force Region page ONLY when explicitly requested (one-shot)
|
||||
if (inkhud->forceRegionMenu) {
|
||||
|
||||
inkhud->forceRegionMenu = false; // consume one-shot flag
|
||||
showPage(MenuPage::REGION);
|
||||
|
||||
} else {
|
||||
showPage(MenuPage::ROOT);
|
||||
}
|
||||
|
||||
// If device has a backlight which isn't controlled by aux button:
|
||||
// backlight on always when menu opens.
|
||||
@@ -139,6 +162,97 @@ int32_t InkHUD::MenuApplet::runOnce()
|
||||
return OSThread::disable();
|
||||
}
|
||||
|
||||
static void applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode region)
|
||||
{
|
||||
if (config.lora.region == region)
|
||||
return;
|
||||
|
||||
config.lora.region = region;
|
||||
|
||||
auto changes = SEGMENT_CONFIG;
|
||||
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
|
||||
if (!owner.is_licensed) {
|
||||
bool keygenSuccess = false;
|
||||
|
||||
if (config.security.private_key.size == 32) {
|
||||
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
|
||||
keygenSuccess = true;
|
||||
}
|
||||
} else {
|
||||
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
|
||||
keygenSuccess = true;
|
||||
}
|
||||
|
||||
if (keygenSuccess) {
|
||||
config.security.public_key.size = 32;
|
||||
config.security.private_key.size = 32;
|
||||
owner.public_key.size = 32;
|
||||
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
config.lora.tx_enabled = true;
|
||||
|
||||
initRegion();
|
||||
|
||||
if (myRegion && myRegion->dutyCycle < 100) {
|
||||
config.lora.ignore_mqtt = true;
|
||||
}
|
||||
|
||||
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
|
||||
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
|
||||
changes |= SEGMENT_MODULECONFIG;
|
||||
}
|
||||
|
||||
service->reloadConfig(changes);
|
||||
|
||||
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
|
||||
}
|
||||
|
||||
static void applyDeviceRole(meshtastic_Config_DeviceConfig_Role role)
|
||||
{
|
||||
if (config.device.role == role)
|
||||
return;
|
||||
|
||||
config.device.role = role;
|
||||
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
|
||||
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
|
||||
}
|
||||
|
||||
static void applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset preset)
|
||||
{
|
||||
if (config.lora.modem_preset == preset)
|
||||
return;
|
||||
|
||||
config.lora.use_preset = true;
|
||||
config.lora.modem_preset = preset;
|
||||
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
|
||||
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
|
||||
}
|
||||
|
||||
static void applyTimezone(const char *tz)
|
||||
{
|
||||
if (!tz || strcmp(config.device.tzdef, tz) == 0)
|
||||
return;
|
||||
|
||||
strncpy(config.device.tzdef, tz, sizeof(config.device.tzdef));
|
||||
config.device.tzdef[sizeof(config.device.tzdef) - 1] = '\0';
|
||||
|
||||
setenv("TZ", config.device.tzdef, 1);
|
||||
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
}
|
||||
|
||||
// Perform action for a menu item, then change page
|
||||
// Behaviors for MenuActions are defined here
|
||||
void InkHUD::MenuApplet::execute(MenuItem item)
|
||||
@@ -150,8 +264,17 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
||||
// Open a submenu without performing any action
|
||||
// Also handles exit
|
||||
case NO_ACTION:
|
||||
if (currentPage == MenuPage::NODE_CONFIG_CHANNELS && item.nextPage == MenuPage::NODE_CONFIG_CHANNEL_DETAIL) {
|
||||
|
||||
// cursor - 1 because index 0 is "Back"
|
||||
selectedChannelIndex = cursor - 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case BACK:
|
||||
showPage(item.nextPage);
|
||||
return;
|
||||
|
||||
case NEXT_TILE:
|
||||
inkhud->nextTile();
|
||||
break;
|
||||
@@ -260,6 +383,317 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
||||
rebootAtMsec = millis() + 2000;
|
||||
break;
|
||||
|
||||
// Power
|
||||
case TOGGLE_POWER_SAVE:
|
||||
#if defined(ARCH_ESP32)
|
||||
config.power.is_power_saving = !config.power.is_power_saving;
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
|
||||
#endif
|
||||
break;
|
||||
|
||||
// Display
|
||||
case TOGGLE_DISPLAY_UNITS:
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
|
||||
config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_METRIC;
|
||||
else
|
||||
config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL;
|
||||
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
break;
|
||||
|
||||
// Bluetooth
|
||||
case TOGGLE_BLUETOOTH:
|
||||
config.bluetooth.enabled = !config.bluetooth.enabled;
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
|
||||
break;
|
||||
|
||||
case TOGGLE_BLUETOOTH_PAIR_MODE:
|
||||
config.bluetooth.fixed_pin = !config.bluetooth.fixed_pin;
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
break;
|
||||
|
||||
// Regions
|
||||
case SET_REGION_US:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_US);
|
||||
break;
|
||||
|
||||
case SET_REGION_EU_868:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_868);
|
||||
break;
|
||||
|
||||
case SET_REGION_EU_433:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_433);
|
||||
break;
|
||||
|
||||
case SET_REGION_CN:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_CN);
|
||||
break;
|
||||
|
||||
case SET_REGION_JP:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_JP);
|
||||
break;
|
||||
|
||||
case SET_REGION_ANZ:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_ANZ);
|
||||
break;
|
||||
case SET_REGION_KR:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KR);
|
||||
break;
|
||||
|
||||
case SET_REGION_TW:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_TW);
|
||||
break;
|
||||
|
||||
case SET_REGION_RU:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_RU);
|
||||
break;
|
||||
|
||||
case SET_REGION_IN:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_IN);
|
||||
break;
|
||||
|
||||
case SET_REGION_NZ_865:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_NZ_865);
|
||||
break;
|
||||
|
||||
case SET_REGION_TH:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_TH);
|
||||
break;
|
||||
|
||||
case SET_REGION_LORA_24:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_LORA_24);
|
||||
break;
|
||||
|
||||
case SET_REGION_UA_433:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_UA_433);
|
||||
break;
|
||||
|
||||
case SET_REGION_UA_868:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_UA_868);
|
||||
break;
|
||||
|
||||
case SET_REGION_MY_433:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_MY_433);
|
||||
break;
|
||||
|
||||
case SET_REGION_MY_919:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_MY_919);
|
||||
break;
|
||||
|
||||
case SET_REGION_SG_923:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_SG_923);
|
||||
break;
|
||||
|
||||
case SET_REGION_PH_433:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_433);
|
||||
break;
|
||||
|
||||
case SET_REGION_PH_868:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_868);
|
||||
break;
|
||||
|
||||
case SET_REGION_PH_915:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_915);
|
||||
break;
|
||||
|
||||
case SET_REGION_ANZ_433:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_ANZ_433);
|
||||
break;
|
||||
|
||||
case SET_REGION_KZ_433:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KZ_433);
|
||||
break;
|
||||
|
||||
case SET_REGION_KZ_863:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KZ_863);
|
||||
break;
|
||||
|
||||
case SET_REGION_NP_865:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_NP_865);
|
||||
break;
|
||||
|
||||
case SET_REGION_BR_902:
|
||||
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_BR_902);
|
||||
break;
|
||||
|
||||
// Roles
|
||||
case SET_ROLE_CLIENT:
|
||||
applyDeviceRole(meshtastic_Config_DeviceConfig_Role_CLIENT);
|
||||
break;
|
||||
|
||||
case SET_ROLE_CLIENT_MUTE:
|
||||
applyDeviceRole(meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE);
|
||||
break;
|
||||
|
||||
case SET_ROLE_ROUTER:
|
||||
applyDeviceRole(meshtastic_Config_DeviceConfig_Role_ROUTER);
|
||||
break;
|
||||
|
||||
case SET_ROLE_REPEATER:
|
||||
applyDeviceRole(meshtastic_Config_DeviceConfig_Role_REPEATER);
|
||||
break;
|
||||
|
||||
// Presets
|
||||
case SET_PRESET_LONG_SLOW:
|
||||
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW);
|
||||
break;
|
||||
|
||||
case SET_PRESET_LONG_MODERATE:
|
||||
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE);
|
||||
break;
|
||||
|
||||
case SET_PRESET_LONG_FAST:
|
||||
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST);
|
||||
break;
|
||||
|
||||
case SET_PRESET_MEDIUM_SLOW:
|
||||
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW);
|
||||
break;
|
||||
|
||||
case SET_PRESET_MEDIUM_FAST:
|
||||
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST);
|
||||
break;
|
||||
|
||||
case SET_PRESET_SHORT_SLOW:
|
||||
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW);
|
||||
break;
|
||||
|
||||
case SET_PRESET_SHORT_FAST:
|
||||
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST);
|
||||
break;
|
||||
|
||||
case SET_PRESET_SHORT_TURBO:
|
||||
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO);
|
||||
break;
|
||||
|
||||
// Timezones
|
||||
case SET_TZ_US_HAWAII:
|
||||
applyTimezone("HST10");
|
||||
break;
|
||||
|
||||
case SET_TZ_US_ALASKA:
|
||||
applyTimezone("AKST9AKDT,M3.2.0,M11.1.0");
|
||||
break;
|
||||
|
||||
case SET_TZ_US_PACIFIC:
|
||||
applyTimezone("PST8PDT,M3.2.0,M11.1.0");
|
||||
break;
|
||||
|
||||
case SET_TZ_US_ARIZONA:
|
||||
applyTimezone("MST7");
|
||||
break;
|
||||
|
||||
case SET_TZ_US_MOUNTAIN:
|
||||
applyTimezone("MST7MDT,M3.2.0,M11.1.0");
|
||||
break;
|
||||
|
||||
case SET_TZ_US_CENTRAL:
|
||||
applyTimezone("CST6CDT,M3.2.0,M11.1.0");
|
||||
break;
|
||||
|
||||
case SET_TZ_US_EASTERN:
|
||||
applyTimezone("EST5EDT,M3.2.0,M11.1.0");
|
||||
break;
|
||||
|
||||
case SET_TZ_BR_BRAZILIA:
|
||||
applyTimezone("BRT3");
|
||||
break;
|
||||
|
||||
case SET_TZ_UTC:
|
||||
applyTimezone("UTC0");
|
||||
break;
|
||||
|
||||
case SET_TZ_EU_WESTERN:
|
||||
applyTimezone("GMT0BST,M3.5.0/1,M10.5.0");
|
||||
break;
|
||||
|
||||
case SET_TZ_EU_CENTRAL:
|
||||
applyTimezone("CET-1CEST,M3.5.0,M10.5.0/3");
|
||||
break;
|
||||
|
||||
case SET_TZ_EU_EASTERN:
|
||||
applyTimezone("EET-2EEST,M3.5.0/3,M10.5.0/4");
|
||||
break;
|
||||
|
||||
case SET_TZ_ASIA_KOLKATA:
|
||||
applyTimezone("IST-5:30");
|
||||
break;
|
||||
|
||||
case SET_TZ_ASIA_HONG_KONG:
|
||||
applyTimezone("HKT-8");
|
||||
break;
|
||||
|
||||
case SET_TZ_AU_AWST:
|
||||
applyTimezone("AWST-8");
|
||||
break;
|
||||
|
||||
case SET_TZ_AU_ACST:
|
||||
applyTimezone("ACST-9:30ACDT,M10.1.0,M4.1.0/3");
|
||||
break;
|
||||
|
||||
case SET_TZ_AU_AEST:
|
||||
applyTimezone("AEST-10AEDT,M10.1.0,M4.1.0/3");
|
||||
break;
|
||||
|
||||
case SET_TZ_PACIFIC_NZ:
|
||||
applyTimezone("NZST-12NZDT,M9.5.0,M4.1.0/3");
|
||||
break;
|
||||
|
||||
// Channels
|
||||
case TOGGLE_CHANNEL_UPLINK: {
|
||||
auto &ch = channels.getByIndex(selectedChannelIndex);
|
||||
ch.settings.uplink_enabled = !ch.settings.uplink_enabled;
|
||||
nodeDB->saveToDisk(SEGMENT_CHANNELS);
|
||||
service->reloadConfig(SEGMENT_CHANNELS);
|
||||
break;
|
||||
}
|
||||
|
||||
case TOGGLE_CHANNEL_DOWNLINK: {
|
||||
auto &ch = channels.getByIndex(selectedChannelIndex);
|
||||
ch.settings.downlink_enabled = !ch.settings.downlink_enabled;
|
||||
nodeDB->saveToDisk(SEGMENT_CHANNELS);
|
||||
service->reloadConfig(SEGMENT_CHANNELS);
|
||||
break;
|
||||
}
|
||||
|
||||
case TOGGLE_CHANNEL_POSITION: {
|
||||
auto &ch = channels.getByIndex(selectedChannelIndex);
|
||||
|
||||
if (!ch.settings.has_module_settings)
|
||||
ch.settings.has_module_settings = true;
|
||||
|
||||
if (ch.settings.module_settings.position_precision > 0)
|
||||
ch.settings.module_settings.position_precision = 0;
|
||||
else
|
||||
ch.settings.module_settings.position_precision = 13; // default
|
||||
|
||||
nodeDB->saveToDisk(SEGMENT_CHANNELS);
|
||||
service->reloadConfig(SEGMENT_CHANNELS);
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_CHANNEL_PRECISION: {
|
||||
auto &ch = channels.getByIndex(selectedChannelIndex);
|
||||
|
||||
if (!ch.settings.has_module_settings)
|
||||
ch.settings.has_module_settings = true;
|
||||
|
||||
// Cursor - 1 because of "Back"
|
||||
uint8_t index = cursor - 1;
|
||||
|
||||
constexpr uint8_t optionCount = sizeof(POSITION_PRECISION_OPTIONS) / sizeof(POSITION_PRECISION_OPTIONS[0]);
|
||||
|
||||
if (index < optionCount) {
|
||||
ch.settings.module_settings.position_precision = POSITION_PRECISION_OPTIONS[index].value;
|
||||
}
|
||||
|
||||
nodeDB->saveToDisk(SEGMENT_CHANNELS);
|
||||
service->reloadConfig(SEGMENT_CHANNELS);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LOG_WARN("Action not implemented");
|
||||
}
|
||||
@@ -275,6 +709,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
{
|
||||
items.clear();
|
||||
items.shrink_to_fit();
|
||||
nodeConfigLabels.clear();
|
||||
|
||||
switch (page) {
|
||||
case ROOT:
|
||||
@@ -285,6 +720,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
items.push_back(MenuItem("Send", MenuPage::SEND));
|
||||
items.push_back(MenuItem("Options", MenuPage::OPTIONS));
|
||||
// items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO
|
||||
items.push_back(MenuItem("Node Config", MenuPage::NODE_CONFIG));
|
||||
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN));
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
@@ -298,6 +734,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
break;
|
||||
|
||||
case OPTIONS:
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT));
|
||||
// Optional: backlight
|
||||
if (settings->optionalMenuItems.backlight)
|
||||
items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
|
||||
@@ -325,29 +762,298 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
&settings->optionalFeatures.notifications));
|
||||
items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS,
|
||||
&settings->optionalFeatures.batteryIcon));
|
||||
|
||||
invertedColors = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
|
||||
items.push_back(MenuItem("Invert Color", MenuAction::TOGGLE_INVERT_COLOR, MenuPage::OPTIONS, &invertedColors));
|
||||
|
||||
items.push_back(
|
||||
MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::OPTIONS, &config.display.use_12h_clock));
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
case APPLETS:
|
||||
populateAppletPage();
|
||||
populateAppletPage(); // must be first
|
||||
items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS));
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
case AUTOSHOW:
|
||||
populateAutoshowPage();
|
||||
populateAutoshowPage(); // must be first
|
||||
items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS));
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
case RECENTS:
|
||||
populateRecentsPage();
|
||||
populateRecentsPage(); // builds only the options
|
||||
items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS));
|
||||
break;
|
||||
|
||||
case NODE_CONFIG:
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT));
|
||||
// Radio Config Section
|
||||
items.push_back(MenuItem::Header("Radio Config"));
|
||||
items.push_back(MenuItem("LoRa", MenuPage::NODE_CONFIG_LORA));
|
||||
items.push_back(MenuItem("Channel", MenuPage::NODE_CONFIG_CHANNELS));
|
||||
// Device Config Section
|
||||
items.push_back(MenuItem::Header("Device Config"));
|
||||
items.push_back(MenuItem("Device", MenuPage::NODE_CONFIG_DEVICE));
|
||||
#if defined(ARCH_ESP32)
|
||||
items.push_back(MenuItem("Power", MenuPage::NODE_CONFIG_POWER));
|
||||
#endif
|
||||
items.push_back(MenuItem("Display", MenuPage::NODE_CONFIG_DISPLAY));
|
||||
items.push_back(MenuItem("Bluetooth", MenuPage::NODE_CONFIG_BLUETOOTH));
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
case NODE_CONFIG_DEVICE: {
|
||||
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
||||
|
||||
const char *role = DisplayFormatters::getDeviceRole(config.device.role);
|
||||
nodeConfigLabels.emplace_back("Role: " + std::string(role));
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_DEVICE_ROLE));
|
||||
|
||||
const char *tz = config.device.tzdef;
|
||||
if (!tz || strlen(tz) == 0)
|
||||
tz = "Unset";
|
||||
|
||||
nodeConfigLabels.emplace_back("Timezone: " + std::string(tz));
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::TIMEZONE));
|
||||
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(ARCH_ESP32)
|
||||
case NODE_CONFIG_POWER: {
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
||||
|
||||
items.push_back(MenuItem("Powersave", MenuAction::TOGGLE_POWER_SAVE, MenuPage::EXIT, &config.power.is_power_saving));
|
||||
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case NODE_CONFIG_DISPLAY: {
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
||||
|
||||
items.push_back(MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::NODE_CONFIG_DISPLAY,
|
||||
&config.display.use_12h_clock));
|
||||
|
||||
const char *unitsLabel =
|
||||
(config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? "Units: Imperial" : "Units: Metric";
|
||||
|
||||
items.push_back(MenuItem(unitsLabel, MenuAction::TOGGLE_DISPLAY_UNITS, MenuPage::NODE_CONFIG_DISPLAY));
|
||||
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_CONFIG_BLUETOOTH: {
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
||||
|
||||
const char *btLabel = config.bluetooth.enabled ? "Bluetooth: On" : "Bluetooth: Off";
|
||||
items.push_back(MenuItem(btLabel, MenuAction::TOGGLE_BLUETOOTH, MenuPage::EXIT));
|
||||
|
||||
const char *pairLabel = config.bluetooth.fixed_pin ? "Pair Mode: Fixed" : "Pair Mode: Random";
|
||||
items.push_back(MenuItem(pairLabel, MenuAction::TOGGLE_BLUETOOTH_PAIR_MODE, MenuPage::NODE_CONFIG_BLUETOOTH));
|
||||
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_CONFIG_LORA: {
|
||||
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
||||
|
||||
const char *region = myRegion ? myRegion->name : "Unset";
|
||||
nodeConfigLabels.emplace_back("Region: " + std::string(region));
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::REGION));
|
||||
|
||||
const char *preset =
|
||||
DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
|
||||
nodeConfigLabels.emplace_back("Preset: " + std::string(preset));
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_PRESET));
|
||||
|
||||
char freqBuf[32];
|
||||
float freq = RadioLibInterface::instance->getFreq();
|
||||
snprintf(freqBuf, sizeof(freqBuf), "Freq: %.3f MHz", freq);
|
||||
nodeConfigLabels.emplace_back(freqBuf);
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_LORA));
|
||||
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_CONFIG_CHANNELS: {
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG));
|
||||
|
||||
for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) {
|
||||
meshtastic_Channel &ch = channels.getByIndex(i);
|
||||
|
||||
if (!ch.has_settings)
|
||||
continue;
|
||||
|
||||
if (ch.role == meshtastic_Channel_Role_DISABLED)
|
||||
continue;
|
||||
|
||||
std::string label = "#";
|
||||
|
||||
if (ch.role == meshtastic_Channel_Role_PRIMARY) {
|
||||
label += "Primary";
|
||||
} else if (strlen(ch.settings.name) > 0) {
|
||||
label += parse(ch.settings.name);
|
||||
} else {
|
||||
label += "Channel" + to_string(i + 1);
|
||||
}
|
||||
|
||||
nodeConfigLabels.push_back(label);
|
||||
items.push_back(
|
||||
MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL));
|
||||
}
|
||||
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_CONFIG_CHANNEL_DETAIL: {
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNELS));
|
||||
|
||||
meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex);
|
||||
|
||||
// Name (read-only)
|
||||
const char *name = strlen(ch.settings.name) > 0 ? ch.settings.name : "Unnamed";
|
||||
nodeConfigLabels.emplace_back("Ch: " + parse(name));
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL));
|
||||
|
||||
// Uplink
|
||||
items.push_back(MenuItem("Uplink", MenuAction::TOGGLE_CHANNEL_UPLINK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL,
|
||||
&ch.settings.uplink_enabled));
|
||||
|
||||
items.push_back(MenuItem("Downlink", MenuAction::TOGGLE_CHANNEL_DOWNLINK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL,
|
||||
&ch.settings.downlink_enabled));
|
||||
|
||||
// Position
|
||||
channelPositionEnabled = ch.settings.has_module_settings && ch.settings.module_settings.position_precision > 0;
|
||||
|
||||
items.push_back(MenuItem("Position", MenuAction::TOGGLE_CHANNEL_POSITION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL,
|
||||
&channelPositionEnabled));
|
||||
|
||||
// Precision
|
||||
if (channelPositionEnabled) {
|
||||
|
||||
std::string precisionLabel = "Unknown";
|
||||
|
||||
for (const auto &opt : POSITION_PRECISION_OPTIONS) {
|
||||
if (opt.value == ch.settings.module_settings.position_precision) {
|
||||
precisionLabel = (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
|
||||
? opt.imperial
|
||||
: opt.metric;
|
||||
break;
|
||||
}
|
||||
}
|
||||
nodeConfigLabels.emplace_back("Precision: " + precisionLabel);
|
||||
items.push_back(
|
||||
MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_PRECISION));
|
||||
}
|
||||
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_CONFIG_CHANNEL_PRECISION: {
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL));
|
||||
meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex);
|
||||
if (!ch.settings.has_module_settings || ch.settings.module_settings.position_precision == 0) {
|
||||
items.push_back(MenuItem("Position is Off", MenuPage::NODE_CONFIG_CHANNEL_DETAIL));
|
||||
break;
|
||||
}
|
||||
constexpr uint8_t optionCount = sizeof(POSITION_PRECISION_OPTIONS) / sizeof(POSITION_PRECISION_OPTIONS[0]);
|
||||
for (uint8_t i = 0; i < optionCount; i++) {
|
||||
const auto &opt = POSITION_PRECISION_OPTIONS[i];
|
||||
const char *label =
|
||||
(config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? opt.imperial : opt.metric;
|
||||
nodeConfigLabels.emplace_back(label);
|
||||
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_CHANNEL_PRECISION,
|
||||
MenuPage::NODE_CONFIG_CHANNEL_DETAIL));
|
||||
}
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_CONFIG_DEVICE_ROLE: {
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE));
|
||||
items.push_back(MenuItem("Client", MenuAction::SET_ROLE_CLIENT, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Client Mute", MenuAction::SET_ROLE_CLIENT_MUTE, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Router", MenuAction::SET_ROLE_ROUTER, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Repeater", MenuAction::SET_ROLE_REPEATER, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
|
||||
case TIMEZONE:
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE));
|
||||
items.push_back(MenuItem("US/Hawaii", SET_TZ_US_HAWAII, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("US/Alaska", SET_TZ_US_ALASKA, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("US/Pacific", SET_TZ_US_PACIFIC, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("US/Arizona", SET_TZ_US_ARIZONA, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("US/Mountain", SET_TZ_US_MOUNTAIN, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("US/Central", SET_TZ_US_CENTRAL, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("US/Eastern", SET_TZ_US_EASTERN, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("BR/Brasilia", SET_TZ_BR_BRAZILIA, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("UTC", SET_TZ_UTC, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("EU/Western", SET_TZ_EU_WESTERN, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("EU/Central", SET_TZ_EU_CENTRAL, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("EU/Eastern", SET_TZ_EU_EASTERN, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Asia/Kolkata", SET_TZ_ASIA_KOLKATA, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Asia/Hong Kong", SET_TZ_ASIA_HONG_KONG, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("AU/AWST", SET_TZ_AU_AWST, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("AU/ACST", SET_TZ_AU_ACST, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("AU/AEST", SET_TZ_AU_AEST, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
case REGION:
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA));
|
||||
items.push_back(MenuItem("US", MenuAction::SET_REGION_US, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("EU 868", MenuAction::SET_REGION_EU_868, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("EU 433", MenuAction::SET_REGION_EU_433, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("CN", MenuAction::SET_REGION_CN, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("JP", MenuAction::SET_REGION_JP, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("ANZ", MenuAction::SET_REGION_ANZ, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("KR", MenuAction::SET_REGION_KR, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("TW", MenuAction::SET_REGION_TW, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("RU", MenuAction::SET_REGION_RU, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("IN", MenuAction::SET_REGION_IN, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("NZ 865", MenuAction::SET_REGION_NZ_865, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("TH", MenuAction::SET_REGION_TH, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("LoRa 2.4", MenuAction::SET_REGION_LORA_24, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("UA 433", MenuAction::SET_REGION_UA_433, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("UA 868", MenuAction::SET_REGION_UA_868, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("MY 433", MenuAction::SET_REGION_MY_433, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("MY 919", MenuAction::SET_REGION_MY_919, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("SG 923", MenuAction::SET_REGION_SG_923, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("PH 433", MenuAction::SET_REGION_PH_433, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("PH 868", MenuAction::SET_REGION_PH_868, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("PH 915", MenuAction::SET_REGION_PH_915, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("ANZ 433", MenuAction::SET_REGION_ANZ_433, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("KZ 433", MenuAction::SET_REGION_KZ_433, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("KZ 863", MenuAction::SET_REGION_KZ_863, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("NP 865", MenuAction::SET_REGION_NP_865, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("BR 902", MenuAction::SET_REGION_BR_902, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
case NODE_CONFIG_PRESET: {
|
||||
items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA));
|
||||
items.push_back(MenuItem("Long Moderate", MenuAction::SET_PRESET_LONG_MODERATE, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Long Fast", MenuAction::SET_PRESET_LONG_FAST, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Medium Slow", MenuAction::SET_PRESET_MEDIUM_SLOW, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Medium Fast", MenuAction::SET_PRESET_MEDIUM_FAST, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Short Slow", MenuAction::SET_PRESET_SHORT_SLOW, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Short Fast", MenuAction::SET_PRESET_SHORT_FAST, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Short Turbo", MenuAction::SET_PRESET_SHORT_TURBO, MenuPage::EXIT));
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
|
||||
case EXIT:
|
||||
sendToBackground(); // Menu applet dismissed, allow normal behavior to resume
|
||||
break;
|
||||
@@ -366,6 +1072,15 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
cursorShown = false;
|
||||
}
|
||||
|
||||
// Ensure cursor never rests on a header
|
||||
if (cursorShown) {
|
||||
while (cursor < items.size() && items.at(cursor).isHeader) {
|
||||
cursor++;
|
||||
}
|
||||
if (cursor >= items.size())
|
||||
cursor = 0;
|
||||
}
|
||||
|
||||
// Remember which page we are on now
|
||||
currentPage = page;
|
||||
}
|
||||
@@ -428,18 +1143,46 @@ void InkHUD::MenuApplet::onRender()
|
||||
|
||||
// -- Loop: draw each (visible) menu item --
|
||||
for (uint8_t i = firstItem; i <= lastItem; i++) {
|
||||
// Grab the menuItem
|
||||
MenuItem item = items.at(i);
|
||||
|
||||
// Center-line for the text
|
||||
// Grab the menu item
|
||||
MenuItem &item = items.at(i);
|
||||
|
||||
// Vertical center of this slot
|
||||
int16_t center = itemT + (itemH / 2);
|
||||
|
||||
// Box, if currently selected
|
||||
if (cursorShown && i == cursor)
|
||||
drawRect(itemL, itemT, itemW, itemH, BLACK);
|
||||
// Header (non-selectable section label)
|
||||
if (item.isHeader) {
|
||||
setFont(fontSmall);
|
||||
|
||||
// Item's text
|
||||
printAt(itemL + X(padding), center, item.label, LEFT, MIDDLE);
|
||||
// Header text (flush left)
|
||||
printAt(itemL + X(padding), center, item.label, LEFT, MIDDLE);
|
||||
|
||||
// Subtle underline
|
||||
int16_t underlineY = itemT + itemH - 2;
|
||||
drawLine(itemL + X(padding), underlineY, itemR - X(padding), underlineY, BLACK);
|
||||
} else {
|
||||
// Box, if currently selected
|
||||
if (cursorShown && i == cursor)
|
||||
drawRect(itemL, itemT, itemW, itemH, BLACK);
|
||||
|
||||
// Indented normal item text
|
||||
printAt(itemL + X(padding * 2), center, item.label, LEFT, MIDDLE);
|
||||
}
|
||||
|
||||
// Checkbox, if relevant
|
||||
if (item.checkState) {
|
||||
const uint16_t cbWH = fontSmall.lineHeight();
|
||||
const int16_t cbL = itemR - X(padding) - cbWH;
|
||||
const int16_t cbT = center - (cbWH / 2);
|
||||
|
||||
if (*(item.checkState)) {
|
||||
drawRect(cbL, cbT, cbWH, cbWH, BLACK);
|
||||
drawLine(cbL + 3, center, cbL + (cbWH / 2), center + (cbWH / 2) - 2, BLACK);
|
||||
drawLine(cbL + (cbWH / 2), center + (cbWH / 2) - 2, cbL + cbWH + 2, center - (cbWH / 2) - 2, BLACK);
|
||||
} else {
|
||||
drawRect(cbL, cbT, cbWH, cbWH, BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
// Checkbox, if relevant
|
||||
if (item.checkState) {
|
||||
@@ -480,10 +1223,13 @@ void InkHUD::MenuApplet::onButtonShortPress()
|
||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||
|
||||
// Move menu cursor to next entry, then update
|
||||
if (cursorShown)
|
||||
cursor = (cursor + 1) % items.size();
|
||||
else
|
||||
if (!cursorShown) {
|
||||
cursorShown = true;
|
||||
} else {
|
||||
do {
|
||||
cursor = (cursor + 1) % items.size();
|
||||
} while (items.at(cursor).isHeader);
|
||||
}
|
||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
||||
void onRender() override;
|
||||
|
||||
void show(Tile *t); // Open the menu, onto a user tile
|
||||
void setStartPage(MenuPage page);
|
||||
|
||||
protected:
|
||||
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
|
||||
@@ -51,13 +52,17 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
||||
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
|
||||
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
|
||||
|
||||
MenuPage startPageOverride = MenuPage::ROOT;
|
||||
MenuPage currentPage = MenuPage::ROOT;
|
||||
uint8_t cursor = 0; // Which menu item is currently highlighted
|
||||
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
|
||||
|
||||
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
|
||||
|
||||
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
||||
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
||||
std::vector<std::string> nodeConfigLabels; // Persistent labels for Node Config pages
|
||||
uint8_t selectedChannelIndex = 0; // Currently selected LoRa channel (Node Config → Radio → Channel)
|
||||
bool channelPositionEnabled = false;
|
||||
|
||||
// Data for selecting and sending canned messages via the menu
|
||||
// Placed into a sub-class for organization only
|
||||
|
||||
@@ -30,6 +30,7 @@ class MenuItem
|
||||
MenuAction action = NO_ACTION;
|
||||
MenuPage nextPage = EXIT;
|
||||
bool *checkState = nullptr;
|
||||
bool isHeader = false; // Non-selectable section label
|
||||
|
||||
// Various constructors, depending on the intended function of the item
|
||||
|
||||
@@ -40,6 +41,12 @@ class MenuItem
|
||||
: label(label), action(action), nextPage(nextPage), checkState(checkState)
|
||||
{
|
||||
}
|
||||
static MenuItem Header(const char *label)
|
||||
{
|
||||
MenuItem item(label, NO_ACTION, EXIT);
|
||||
item.isHeader = true;
|
||||
return item;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
@@ -20,10 +20,23 @@ enum MenuPage : uint8_t {
|
||||
SEND,
|
||||
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
|
||||
OPTIONS,
|
||||
NODE_CONFIG,
|
||||
NODE_CONFIG_LORA,
|
||||
NODE_CONFIG_CHANNELS, // List of channels
|
||||
NODE_CONFIG_CHANNEL_DETAIL, // Per-channel options
|
||||
NODE_CONFIG_CHANNEL_PRECISION,
|
||||
NODE_CONFIG_PRESET,
|
||||
NODE_CONFIG_DEVICE,
|
||||
NODE_CONFIG_DEVICE_ROLE,
|
||||
NODE_CONFIG_POWER,
|
||||
NODE_CONFIG_DISPLAY,
|
||||
NODE_CONFIG_BLUETOOTH,
|
||||
TIMEZONE,
|
||||
APPLETS,
|
||||
AUTOSHOW,
|
||||
RECENTS, // Select length of "recentlyActiveSeconds"
|
||||
EXIT, // Dismiss the menu applet
|
||||
REGION,
|
||||
EXIT, // Dismiss the menu applet
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
@@ -10,15 +10,14 @@ using namespace NicheGraphics;
|
||||
|
||||
InkHUD::TipsApplet::TipsApplet()
|
||||
{
|
||||
// Decide which tips (if any) should be shown to user after the boot screen
|
||||
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
|
||||
|
||||
// Welcome screen
|
||||
if (settings->tips.firstBoot)
|
||||
tipQueue.push_back(Tip::WELCOME);
|
||||
|
||||
// Antenna, region, timezone
|
||||
// Shown at boot if region not yet set
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
||||
// Finish setup (info only, not picker)
|
||||
if (needsRegion)
|
||||
tipQueue.push_back(Tip::FINISH_SETUP);
|
||||
|
||||
// Shutdown info
|
||||
@@ -36,8 +35,10 @@ InkHUD::TipsApplet::TipsApplet()
|
||||
if (config.display.flip_screen)
|
||||
tipQueue.push_back(Tip::ROTATION);
|
||||
|
||||
// Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground
|
||||
// LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector
|
||||
// Region picker
|
||||
if (needsRegion)
|
||||
tipQueue.push_back(Tip::PICK_REGION);
|
||||
|
||||
if (!tipQueue.empty())
|
||||
bringToForeground();
|
||||
}
|
||||
@@ -51,84 +52,123 @@ void InkHUD::TipsApplet::onRender()
|
||||
|
||||
case Tip::FINISH_SETUP: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Tip: Finish Setup");
|
||||
const char *title = "Tip: Finish Setup";
|
||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
||||
printWrapped(0, 0, width(), title);
|
||||
|
||||
setFont(fontSmall);
|
||||
int16_t cursorY = fontMedium.lineHeight() * 1.5;
|
||||
printAt(0, cursorY, "- connect antenna");
|
||||
int16_t cursorY = h + fontSmall.lineHeight();
|
||||
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- connect a client app");
|
||||
auto drawBullet = [&](const char *text) {
|
||||
uint16_t bh = getWrappedTextHeight(0, width(), text);
|
||||
printWrapped(0, cursorY, width(), text);
|
||||
cursorY += bh + (fontSmall.lineHeight() / 3);
|
||||
};
|
||||
|
||||
// Only if region not set
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- set region");
|
||||
}
|
||||
drawBullet("- connect antenna");
|
||||
drawBullet("- connect a client app");
|
||||
|
||||
// Only if tz not set
|
||||
if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) {
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- set timezone");
|
||||
}
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
||||
drawBullet("- set region");
|
||||
|
||||
cursorY += fontSmall.lineHeight() * 1.5;
|
||||
printAt(0, cursorY, "More info at meshtastic.org");
|
||||
if (!(*config.device.tzdef && config.device.tzdef[0] != 0))
|
||||
drawBullet("- set timezone");
|
||||
|
||||
cursorY += fontSmall.lineHeight() / 2;
|
||||
drawBullet("More info at meshtastic.org");
|
||||
|
||||
setFont(fontSmall);
|
||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||
} break;
|
||||
|
||||
case Tip::PICK_REGION: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Set Region");
|
||||
|
||||
setFont(fontSmall);
|
||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "Please select your LoRa region to complete setup.");
|
||||
|
||||
printAt(0, Y(1.0), "Press button to choose", LEFT, BOTTOM);
|
||||
} break;
|
||||
|
||||
case Tip::SAFE_SHUTDOWN: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Tip: Shutdown");
|
||||
|
||||
const char *title = "Tip: Shutdown";
|
||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
||||
printWrapped(0, 0, width(), title);
|
||||
|
||||
setFont(fontSmall);
|
||||
std::string shutdown;
|
||||
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n";
|
||||
shutdown += "\n";
|
||||
shutdown += "This ensures data is saved.";
|
||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown);
|
||||
int16_t cursorY = h + fontSmall.lineHeight();
|
||||
|
||||
const char *body = "Before removing power, please shut down from InkHUD menu, or a client app.\n\n"
|
||||
"This ensures data is saved.";
|
||||
|
||||
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
|
||||
printWrapped(0, cursorY, width(), body);
|
||||
cursorY += bodyH + (fontSmall.lineHeight() / 2);
|
||||
|
||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||
|
||||
} break;
|
||||
|
||||
case Tip::CUSTOMIZATION: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Tip: Customization");
|
||||
|
||||
const char *title = "Tip: Customization";
|
||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
||||
printWrapped(0, 0, width(), title);
|
||||
|
||||
setFont(fontSmall);
|
||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||
"Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more.");
|
||||
int16_t cursorY = h + fontSmall.lineHeight();
|
||||
|
||||
const char *body = "Configure & control display with the InkHUD menu. "
|
||||
"Optional features, layout, rotation, and more.";
|
||||
|
||||
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
|
||||
printWrapped(0, cursorY, width(), body);
|
||||
cursorY += bodyH + (fontSmall.lineHeight() / 2);
|
||||
|
||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||
} break;
|
||||
|
||||
case Tip::BUTTONS: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Tip: Buttons");
|
||||
|
||||
const char *title = "Tip: Buttons";
|
||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
||||
printWrapped(0, 0, width(), title);
|
||||
|
||||
setFont(fontSmall);
|
||||
int16_t cursorY = fontMedium.lineHeight() * 1.5;
|
||||
int16_t cursorY = h + fontSmall.lineHeight();
|
||||
|
||||
printAt(0, cursorY, "User Button");
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- short press: next");
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- long press: select / open menu");
|
||||
cursorY += fontSmall.lineHeight() * 1.5;
|
||||
auto drawBullet = [&](const char *text) {
|
||||
uint16_t bh = getWrappedTextHeight(0, width(), text);
|
||||
printWrapped(0, cursorY, width(), text);
|
||||
cursorY += bh + (fontSmall.lineHeight() / 3);
|
||||
};
|
||||
|
||||
drawBullet("User Button");
|
||||
drawBullet("- short press: next");
|
||||
drawBullet("- long press: select / open menu");
|
||||
|
||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||
} break;
|
||||
|
||||
case Tip::ROTATION: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Tip: Rotation");
|
||||
|
||||
const char *title = "Tip: Rotation";
|
||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
||||
printWrapped(0, 0, width(), title);
|
||||
|
||||
setFont(fontSmall);
|
||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
|
||||
int16_t cursorY = h + fontSmall.lineHeight();
|
||||
|
||||
const char *body = "To rotate the display, use the InkHUD menu. "
|
||||
"Long-press the user button > Options > Rotate.";
|
||||
|
||||
uint16_t bh = getWrappedTextHeight(0, width(), body);
|
||||
printWrapped(0, cursorY, width(), body);
|
||||
cursorY += bh + (fontSmall.lineHeight() / 2);
|
||||
|
||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||
|
||||
@@ -145,12 +185,15 @@ void InkHUD::TipsApplet::renderWelcome()
|
||||
{
|
||||
uint16_t padW = X(0.05);
|
||||
|
||||
// Detect portrait orientation
|
||||
bool portrait = height() > width();
|
||||
|
||||
// Block 1 - logo & title
|
||||
// ========================
|
||||
|
||||
// Logo size
|
||||
uint16_t logoWLimit = X(0.3);
|
||||
uint16_t logoHLimit = Y(0.3);
|
||||
uint16_t logoWLimit = portrait ? X(0.5) : X(0.3);
|
||||
uint16_t logoHLimit = portrait ? Y(0.25) : Y(0.3);
|
||||
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
|
||||
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
|
||||
|
||||
@@ -163,7 +206,7 @@ void InkHUD::TipsApplet::renderWelcome()
|
||||
|
||||
// Center the block
|
||||
// Desired effect: equal margin from display edge for logo left and title right
|
||||
int16_t block1Y = Y(0.3);
|
||||
int16_t block1Y = portrait ? Y(0.2) : Y(0.3);
|
||||
int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2);
|
||||
int16_t logoCX = block1CX - (logoW / 2) - (padW / 2);
|
||||
int16_t titleCX = block1CX + (titleW / 2) + (padW / 2);
|
||||
@@ -178,7 +221,7 @@ void InkHUD::TipsApplet::renderWelcome()
|
||||
std::string subtitle = "InkHUD";
|
||||
if (width() >= 200)
|
||||
subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display
|
||||
printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE);
|
||||
printAt(X(0.5), portrait ? Y(0.45) : Y(0.6), subtitle, CENTER, MIDDLE);
|
||||
|
||||
// Block 3 - press to continue
|
||||
// ============================
|
||||
@@ -210,6 +253,19 @@ void InkHUD::TipsApplet::onBackground()
|
||||
// While our SystemApplet::handleInput flag is true
|
||||
void InkHUD::TipsApplet::onButtonShortPress()
|
||||
{
|
||||
// If we're prompting the user to pick a region, hand off to the menu
|
||||
if (!tipQueue.empty() && tipQueue.front() == Tip::PICK_REGION) {
|
||||
tipQueue.pop_front();
|
||||
|
||||
// Signal InkHUD to open the menu on Region page
|
||||
inkhud->forceRegionMenu = true;
|
||||
|
||||
// Close tips and open menu
|
||||
sendToBackground();
|
||||
inkhud->openMenu();
|
||||
return;
|
||||
}
|
||||
// Consume current tip
|
||||
tipQueue.pop_front();
|
||||
|
||||
// All tips done
|
||||
@@ -221,15 +277,15 @@ void InkHUD::TipsApplet::onButtonShortPress()
|
||||
inkhud->persistence->saveSettings();
|
||||
}
|
||||
|
||||
// Close applet, and full refresh to clean the screen
|
||||
// Need to force update, because our request would be ignored otherwise, as we are now background
|
||||
// Close applet and clean the screen
|
||||
sendToBackground();
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
}
|
||||
|
||||
// More tips left
|
||||
else
|
||||
else {
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -23,6 +23,7 @@ class TipsApplet : public SystemApplet
|
||||
enum class Tip {
|
||||
WELCOME,
|
||||
FINISH_SETUP,
|
||||
PICK_REGION,
|
||||
SAFE_SHUTDOWN,
|
||||
CUSTOMIZATION,
|
||||
BUTTONS,
|
||||
|
||||
@@ -66,6 +66,9 @@ class InkHUD
|
||||
void rotate();
|
||||
void toggleBatteryIcon();
|
||||
|
||||
// Used by TipsApplet to force menu to start on Region selection
|
||||
bool forceRegionMenu = false;
|
||||
|
||||
// Updating the display
|
||||
// - called by various InkHUD components
|
||||
|
||||
|
||||
@@ -756,12 +756,12 @@ This mapping of emoji to control characters is fairly arbitrary. Selection was i
|
||||
| `0x03` | 🙂 |
|
||||
| `0x04` | 😆 |
|
||||
| `0x05` | 👋 |
|
||||
| `0x06` | ☀ |
|
||||
| `0x06` | ☀ |
|
||||
| ~~`0x07`~~ | (bell char, unused) |
|
||||
| `0x08` | 🌧 |
|
||||
| `0x09` | ☁ |
|
||||
| `0x09` | ☁ |
|
||||
| ~~`0x0A`~~ | (line feed, unused) |
|
||||
| `0x0B` | ♥ |
|
||||
| `0x0B` | ♥ |
|
||||
| `0x0C` | 💩 |
|
||||
| ~~`0x0D`~~ | (carriage return, unused) |
|
||||
| `0x0E` | 🔔 |
|
||||
|
||||
@@ -311,10 +311,7 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset {
|
||||
/* Short Range - Turbo
|
||||
This is the fastest preset and the only one with 500kHz bandwidth.
|
||||
It is not legal to use in all regions due to this wider bandwidth. */
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO = 8,
|
||||
/* Long Range - Turbo
|
||||
This preset performs similarly to LongFast, but with 500Khz bandwidth. */
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO = 9
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO = 8
|
||||
} meshtastic_Config_LoRaConfig_ModemPreset;
|
||||
|
||||
typedef enum _meshtastic_Config_BluetoothConfig_PairingMode {
|
||||
@@ -692,8 +689,8 @@ extern "C" {
|
||||
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_BR_902+1))
|
||||
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO+1))
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO+1))
|
||||
|
||||
#define _meshtastic_Config_BluetoothConfig_PairingMode_MIN meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN
|
||||
#define _meshtastic_Config_BluetoothConfig_PairingMode_MAX meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN
|
||||
|
||||
@@ -417,6 +417,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
}
|
||||
case meshtastic_AdminMessage_enter_dfu_mode_request_tag: {
|
||||
LOG_INFO("Client requesting to enter DFU mode");
|
||||
#if HAS_SCREEN
|
||||
IF_SCREEN(screen->showSimpleBanner("Device is rebooting\ninto DFU mode.", 0));
|
||||
#endif
|
||||
#if defined(ARCH_NRF52) || defined(ARCH_RP2040)
|
||||
enterDfuMode();
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user