Compare commits

...
Author SHA1 Message Date
Jonathan Bennett 5f5698ccc0 Explicitly include meshUtils.h 2025-07-10 10:29:33 -05:00
Jonathan Bennett 74c735d5fb Gate screen code behind IF_SCREEN() 2025-07-10 10:20:44 -05:00
Jonathan Bennett 107dec22bd Remove bogus validation check 2025-07-10 10:12:02 -05:00
Jonathan Bennett 0795b21c2b Key verification flow on BaseUI (#7240) 2025-07-10 09:45:36 -05:00
renovate[bot] a7e516d6f6 Update Adafruit INA260 to v1.5.3 (#7270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 15:11:05 +08:00
Austin f6d378255c Actions: Re-Add nrf52 hex release (rak4631) (#7276) 2025-07-08 23:53:51 -04:00
Austin 19d831d20d Whoops! Re-Add nRF52 OTA zips (#7275) 2025-07-08 21:33:59 -04:00
AustinandBen Meadors 00495140bd GitHub Actions faster!! (#7268)
Use new meshtastic/gh-action-firmware Action

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-08 15:14:05 -05:00
Ben Meadors 354f149338 Make PacketHistory logging less chatty (#7272) 2025-07-08 15:12:44 -05:00
Jason P 999e1207a5 Show user which option is currently elected (#7271) 2025-07-08 14:38:38 -05:00
Jason P 916587c2a6 Update Bluetooth Toggle to match other variants (#7269) 2025-07-08 13:38:07 -05:00
todd-herbert db4e4e6e53 Heltec Wireless Paper, VM-E213 Hardware Revisions (#7258)
* Tests to identify display model

* (InkHUD) SSD1682 controller IC
Has a few quirks, gets its own base class

* (InkHUD) E0213A367 Display
For Heltec Wireless Paper V1.1.1, V1.2
For Heltec VM-E213 V1.1

* (InkHUD) Select display model at boot

* (BaseUI) Wrapper to combine multiple GxEPD2 drivers
Workaround for issue of GxEPD2_BW objects not having a shared base class. Allows us to select a driver at runtime.
https://github.com/meshtastic/firmware/issues/6851#issuecomment-2905353447

* (BaseUI) Select E-Ink model at boot

* (InkHUD) SSD1682 deep sleep

* (InkHUD) No deep sleep for SSD1682

* (InkHUD) Fully no-op deep sleep for SSD1682
2025-07-08 13:01:48 -05:00
Jason P 9c08220d24 TFT_MESH Fixes Across Various Devices (#7247)
* Rename "r,g,b" variables to having a TFT_MESH_ prefix

* Reboot and then user options

* Restore TFT_MESH on any ST7789Spi driver
2025-07-08 06:24:12 -05:00
HarukiToreda 88b299dd41 Modules and favorite screen fix (#7264)
* T-watch screen misalignment fix

* Trunk fix

* Fix for favorite frame when module screen is enabled
2025-07-08 06:22:57 -05:00
github-actions[bot]andsachaw 19af2d9e3b Upgrade trunk (#7266)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-07-08 06:22:24 -05:00
Jonathan Bennett fa23be4424 Revert "GitHub Actions faster!! (#7244)" (#7262)
This reverts commit f2fb473ecf.
2025-07-07 19:50:44 -05:00
Ing. Jan Kaláb e1f40c2db9 Fix install script (#7259)
This partially reverse 2ab717c (#7143), fixing the install script. It looks like a bad/missed copy/paste.
2025-07-07 19:36:21 -05:00
Ben Meadors 415dc4aa47 Try-fix: L76K spamming bad times can crash nodes (#7261)
* Try-fix: Clear GPS buffer when we encounter a bad time in NMEA

* Fix signed int warnings
2025-07-07 19:35:57 -05:00
AustinandBen Meadors f2fb473ecf GitHub Actions faster!! (#7244)
Use new meshtastic/gh-action-firmware Action

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-07 19:34:25 -05:00
Max f95c77b8bd Fast fix, remove saving tx power inside limitPower() (#7255) 2025-07-07 07:50:13 -05:00
github-actions[bot]andsachaw 09d4ee1ea7 Upgrade trunk (#7254)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-07-07 06:37:15 -05:00
Jonathan Bennett 40c586ca97 Automatically bail user out of displaymode_color when not HAS_TFT (#7248) 2025-07-06 16:36:22 -05:00
49 changed files with 974 additions and 324 deletions
+19 -16
View File
@@ -11,27 +11,30 @@ permissions: read-all
jobs: jobs:
build-esp32: build-esp32:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build ESP32 - name: Build ESP32
id: build id: build
uses: ./.github/actions/build-variant uses: meshtastic/gh-action-firmware@main
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} pio_platform: esp32
board: ${{ inputs.board }} pio_env: ${{ inputs.board }}
remove-debug-flags: >- pio_target: build
./arch/esp32/esp32.ini ota_firmware_source: firmware.bin
./arch/esp32/esp32s2.ini ota_firmware_target: release/bleota.bin
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini - name: Store binaries as an artifact
./arch/esp32/esp32c6.ini uses: actions/upload-artifact@v4
build-script-path: bin/build-esp32.sh with:
ota-firmware-source: firmware.bin name: firmware-esp32-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
ota-firmware-target: release/bleota.bin overwrite: true
artifact-paths: | path: |
release/*.bin release/*.bin
release/*.elf release/*.elf
#include-web-ui: true
arch: esp32
+19 -16
View File
@@ -11,27 +11,30 @@ permissions: read-all
jobs: jobs:
build-esp32-c3: build-esp32-c3:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build ESP32-C3 - name: Build ESP32-C3
id: build id: build
uses: ./.github/actions/build-variant uses: meshtastic/gh-action-firmware@main
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} pio_platform: esp32
board: ${{ inputs.board }} pio_env: ${{ inputs.board }}
remove-debug-flags: >- pio_target: build
./arch/esp32/esp32.ini ota_firmware_source: firmware-c3.bin
./arch/esp32/esp32s2.ini ota_firmware_target: release/bleota-c3.bin
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini - name: Store binaries as an artifact
./arch/esp32/esp32c6.ini uses: actions/upload-artifact@v4
build-script-path: bin/build-esp32.sh with:
ota-firmware-source: firmware-c3.bin name: firmware-esp32c3-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
ota-firmware-target: release/bleota-c3.bin overwrite: true
artifact-paths: | path: |
release/*.bin release/*.bin
release/*.elf release/*.elf
#include-web-ui: true
arch: esp32c3
+19 -16
View File
@@ -11,27 +11,30 @@ permissions: read-all
jobs: jobs:
build-esp32-c6: build-esp32-c6:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build ESP32-C6 - name: Build ESP32-C6
id: build id: build
uses: ./.github/actions/build-variant uses: meshtastic/gh-action-firmware@main
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} pio_platform: esp32
board: ${{ inputs.board }} pio_env: ${{ inputs.board }}
remove-debug-flags: >- pio_target: build
./arch/esp32/esp32.ini ota_firmware_source: firmware-c3.bin
./arch/esp32/esp32s2.ini ota_firmware_target: release/bleota-c3.bin
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini - name: Store binaries as an artifact
./arch/esp32/esp32c6.ini uses: actions/upload-artifact@v4
build-script-path: bin/build-esp32.sh with:
ota-firmware-source: firmware-c3.bin name: firmware-esp32c6-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
ota-firmware-target: release/bleota-c3.bin overwrite: true
artifact-paths: | path: |
release/*.bin release/*.bin
release/*.elf release/*.elf
#include-web-ui: true
arch: esp32c6
+19 -16
View File
@@ -11,27 +11,30 @@ permissions: read-all
jobs: jobs:
build-esp32-s3: build-esp32-s3:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build ESP32-S3 - name: Build ESP32-S3
id: build id: build
uses: ./.github/actions/build-variant uses: meshtastic/gh-action-firmware@main
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} pio_platform: esp32
board: ${{ inputs.board }} pio_env: ${{ inputs.board }}
remove-debug-flags: >- pio_target: build
./arch/esp32/esp32.ini ota_firmware_source: firmware-s3.bin
./arch/esp32/esp32s2.ini ota_firmware_target: release/bleota-s3.bin
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini - name: Store binaries as an artifact
./arch/esp32/esp32c6.ini uses: actions/upload-artifact@v4
build-script-path: bin/build-esp32.sh with:
ota-firmware-source: firmware-s3.bin name: firmware-esp32s3-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
ota-firmware-target: release/bleota-s3.bin overwrite: true
artifact-paths: | path: |
release/*.bin release/*.bin
release/*.elf release/*.elf
#include-web-ui: true
arch: esp32s3
+19 -9
View File
@@ -11,20 +11,30 @@ permissions: read-all
jobs: jobs:
build-nrf52: build-nrf52:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build NRF52 - name: Build NRF52
id: build id: build
uses: ./.github/actions/build-variant uses: meshtastic/gh-action-firmware@main
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} pio_platform: nrf52
board: ${{ inputs.board }} pio_env: ${{ inputs.board }}
build-script-path: bin/build-nrf52.sh pio_target: build
artifact-paths: |
release/*.hex - name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-nrf52840-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
release/*.uf2 release/*.uf2
release/*.elf release/*.elf
release/*.zip release/*.hex
arch: nrf52840 release/*-ota.zip
+17 -7
View File
@@ -11,18 +11,28 @@ permissions: read-all
jobs: jobs:
build-rpi2040: build-rpi2040:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build Raspberry Pi 2040 - name: Build Raspberry Pi 2040
id: build id: build
uses: ./.github/actions/build-variant uses: meshtastic/gh-action-firmware@main
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} pio_platform: rp2xx0
board: ${{ inputs.board }} pio_env: ${{ inputs.board }}
build-script-path: bin/build-rpi2040.sh pio_target: build
artifact-paths: |
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-rp2040-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
release/*.uf2 release/*.uf2
release/*.elf release/*.elf
arch: rp2040
+17 -7
View File
@@ -11,19 +11,29 @@ permissions: read-all
jobs: jobs:
build-stm32: build-stm32:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build STM32WL - name: Build STM32WL
id: build id: build
uses: ./.github/actions/build-variant uses: meshtastic/gh-action-firmware@main
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} pio_platform: stm32wl
board: ${{ inputs.board }} pio_env: ${{ inputs.board }}
build-script-path: bin/build-stm32.sh pio_target: build
artifact-paths: |
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-stm32-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
release/*.hex release/*.hex
release/*.bin release/*.bin
release/*.elf release/*.elf
arch: stm32
+2 -1
View File
@@ -135,6 +135,7 @@ jobs:
board: ${{ matrix.board }} board: ${{ matrix.board }}
build-debian-src: build-debian-src:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/build_debian_src.yml uses: ./.github/workflows/build_debian_src.yml
with: with:
series: UNRELEASED series: UNRELEASED
@@ -425,7 +426,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-firmware: publish-firmware:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: ${{ github.event_name == 'workflow_dispatch' }} if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-firmware] needs: [release-firmware]
env: env:
+2
View File
@@ -8,6 +8,7 @@ permissions: read-all
jobs: jobs:
trunk_check: trunk_check:
if: github.repository == 'meshtastic/firmware'
name: Trunk Check and Upload name: Trunk Check and Upload
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -21,6 +22,7 @@ jobs:
trunk-token: ${{ secrets.TRUNK_TOKEN }} trunk-token: ${{ secrets.TRUNK_TOKEN }}
trunk_upgrade: trunk_upgrade:
if: github.repository == 'meshtastic/firmware'
# See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#automatic-upgrades # See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#automatic-upgrades
name: Trunk Upgrade (PR) name: Trunk Upgrade (PR)
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -13,6 +13,7 @@ permissions:
jobs: jobs:
semgrep-full: semgrep-full:
if: github.repository == 'meshtastic/firmware'
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
container: container:
image: semgrep/semgrep image: semgrep/semgrep
+1
View File
@@ -11,6 +11,7 @@ permissions:
jobs: jobs:
stale_issues: stale_issues:
if: github.repository == 'meshtastic/firmware'
name: Close Stale Issues name: Close Stale Issues
runs-on: ubuntu-latest runs-on: ubuntu-latest
+2
View File
@@ -12,9 +12,11 @@ permissions:
jobs: jobs:
native-tests: native-tests:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/test_native.yml uses: ./.github/workflows/test_native.yml
hardware-tests: hardware-tests:
if: github.repository == 'meshtastic/firmware'
runs-on: test-runner runs-on: test-runner
steps: steps:
- name: Checkout code - name: Checkout code
+4 -4
View File
@@ -9,14 +9,14 @@ plugins:
lint: lint:
enabled: enabled:
- checkov@3.2.447 - checkov@3.2.447
- renovate@41.17.2 - renovate@41.23.4
- prettier@3.6.2 - prettier@3.6.2
- trufflehog@3.89.2 - trufflehog@3.89.2
- yamllint@1.37.1 - yamllint@1.37.1
- bandit@1.8.5 - bandit@1.8.6
- trivy@0.64.0 - trivy@0.64.1
- taplo@0.9.3 - taplo@0.9.3
- ruff@0.12.1 - ruff@0.12.2
- isort@6.0.1 - isort@6.0.1
- markdownlint@0.45.0 - markdownlint@0.45.0
- oxipng@9.1.5 - oxipng@9.1.5
+1 -6
View File
@@ -7,12 +7,7 @@ MCU=""
# Variant groups # Variant groups
BIGDB_8MB=( BIGDB_8MB=(
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly. "picomputer-s3"
if [[ $FILENAME == *"-tft-"* ]]; then
TFT_BUILD=true
fi
# Extract BASENAME from %FILENAME% for later use.r-s3"
"unphone" "unphone"
"seeed-sensecap-indicator" "seeed-sensecap-indicator"
"crowpanel-esp32s3" "crowpanel-esp32s3"
+2 -1
View File
@@ -10,7 +10,8 @@
"hwids": [ "hwids": [
["0x239A", "0x4405"], ["0x239A", "0x4405"],
["0x239A", "0x0029"], ["0x239A", "0x0029"],
["0x239A", "0x002A"] ["0x239A", "0x002A"],
["0x2886", "0x1667"]
], ],
"usb_product": "HT-n5262", "usb_product": "HT-n5262",
"mcu": "nrf52840", "mcu": "nrf52840",
+1 -1
View File
@@ -129,7 +129,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library # renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library
adafruit/Adafruit MCP9808 Library@2.0.2 adafruit/Adafruit MCP9808 Library@2.0.2
# renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library # renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library
adafruit/Adafruit INA260 Library@1.5.2 adafruit/Adafruit INA260 Library@1.5.3
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219 # renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3 adafruit/Adafruit INA219@1.2.3
# renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor # renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor
+4 -1
View File
@@ -1536,7 +1536,10 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
if (t.tm_mon > -1) { if (t.tm_mon > -1) {
LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min, LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min,
t.tm_sec, ti.age()); t.tm_sec, ti.age());
perhapsSetRTC(RTCQualityGPS, t); if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) {
// Clear the GPS buffer if we got an invalid time
clearBuffer();
}
return true; return true;
} else } else
return false; return false;
+6 -6
View File
@@ -105,7 +105,7 @@ void readFromRTC()
* *
* If we haven't yet set our RTC this boot, set it from a GPS derived time * If we haven't yet set our RTC this boot, set it from a GPS derived time
*/ */
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
{ {
static uint32_t lastSetMsec = 0; static uint32_t lastSetMsec = 0;
uint32_t now = millis(); uint32_t now = millis();
@@ -113,7 +113,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
#ifdef BUILD_EPOCH #ifdef BUILD_EPOCH
if (tv->tv_sec < BUILD_EPOCH) { if (tv->tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return false; return RTCSetResultInvalidTime;
} }
#endif #endif
@@ -184,9 +184,9 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
readFromRTC(); readFromRTC();
#endif #endif
return true; return RTCSetResultSuccess;
} else { } else {
return false; return RTCSetResultNotSet; // RTC was already set with a higher quality time
} }
} }
@@ -215,7 +215,7 @@ const char *RtcName(RTCQuality quality)
* @param t The time to potentially set the RTC to. * @param t The time to potentially set the RTC to.
* @return True if the RTC was set to the provided time, false otherwise. * @return True if the RTC was set to the provided time, false otherwise.
*/ */
bool perhapsSetRTC(RTCQuality q, struct tm &t) RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
{ {
/* Convert to unix time /* Convert to unix time
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
@@ -231,7 +231,7 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
// LOG_DEBUG("Got time from GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec); // LOG_DEBUG("Got time from GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec);
if (t.tm_year < 0 || t.tm_year >= 300) { if (t.tm_year < 0 || t.tm_year >= 300) {
// LOG_DEBUG("Ignore invalid GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec); // LOG_DEBUG("Ignore invalid GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec);
return false; return RTCSetResultInvalidTime;
} else { } else {
return perhapsSetRTC(q, &tv); return perhapsSetRTC(q, &tv);
} }
+11 -2
View File
@@ -22,13 +22,22 @@ enum RTCQuality {
RTCQualityGPS = 4 RTCQualityGPS = 4
}; };
/// The RTC set result codes
/// Used to indicate the result of an attempt to set the RTC.
enum RTCSetResult {
RTCSetResultNotSet = 0, ///< RTC was set successfully
RTCSetResultSuccess = 1, ///< RTC was set successfully
RTCSetResultInvalidTime = 3, ///< The provided time was invalid (e.g., before the build epoch)
RTCSetResultError = 4 ///< An error occurred while setting the RTC
};
RTCQuality getRTCQuality(); RTCQuality getRTCQuality();
extern uint32_t lastSetFromPhoneNtpOrGps; extern uint32_t lastSetFromPhoneNtpOrGps;
/// If we haven't yet set our RTC this boot, set it from a GPS derived time /// If we haven't yet set our RTC this boot, set it from a GPS derived time
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false); RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
bool perhapsSetRTC(RTCQuality q, struct tm &t); RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t);
/// Return a string name for the quality /// Return a string name for the quality
const char *RtcName(RTCQuality quality); const char *RtcName(RTCQuality quality);
+23 -3
View File
@@ -6,6 +6,10 @@
#include "main.h" #include "main.h"
#include <SPI.h> #include <SPI.h>
#ifdef GXEPD2_DRIVER_0
#include "einkDetect.h"
#endif
/* /*
The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini
Previously, these macros were defined at the top of this file. Previously, these macros were defined at the top of this file.
@@ -174,9 +178,8 @@ bool EInkDisplay::connect()
} }
} }
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \ #elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || \
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \ defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
{ {
// Start HSPI // Start HSPI
hspi = new SPIClass(HSPI); hspi = new SPIClass(HSPI);
@@ -232,6 +235,23 @@ bool EInkDisplay::connect()
adafruitDisplay->init(); adafruitDisplay->init();
adafruitDisplay->setRotation(3); adafruitDisplay->setRotation(3);
} }
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
// Detect display model, before starting SPI
EInkDetectionResult displayModel = detectEInk();
// Start HSPI
hspi = new SPIClass(HSPI);
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
// Create GxEPD2 object
adafruitDisplay = new GxEPD2_Multi<GXEPD2_DRIVER_0, GXEPD2_DRIVER_1>((uint8_t)displayModel, PIN_EINK_CS, PIN_EINK_DC,
PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
// Init GxEPD2
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
#endif #endif
return true; return true;
+12 -1
View File
@@ -5,6 +5,10 @@
#include "GxEPD2_BW.h" #include "GxEPD2_BW.h"
#include <OLEDDisplay.h> #include <OLEDDisplay.h>
#ifdef GXEPD2_DRIVER_0 // If variant has multiple possible display models
#include "GxEPD2Multi.h"
#endif
/** /**
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation. * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
* *
@@ -63,8 +67,15 @@ class EInkDisplay : public OLEDDisplay
// Connect to the display // Connect to the display
virtual bool connect() override; virtual bool connect() override;
// AdafruitGFX display object - instantiated in connect(), variant specific #ifdef GXEPD2_DRIVER_0
// AdafruitGFX display object - wrapper for multiple drivers
// Allows runtime detection of multiple displays
// Avoid this situation if possible!
GxEPD2_Multi<GXEPD2_DRIVER_0, GXEPD2_DRIVER_1> *adafruitDisplay = NULL;
#else
// AdafruitGFX display object (for single display model) - instantiated in connect(), variant specific
GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> *adafruitDisplay = NULL; GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> *adafruitDisplay = NULL;
#endif
// If display uses HSPI // If display uses HSPI
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ #if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
+135
View File
@@ -0,0 +1,135 @@
// Wrapper class for GxEPD2_BW
// Generic signature at build-time, so that we can detect display model at run-time
// Workaround for issue of GxEPD2_BW objects not having a shared base class
// Only exposes methods which we are actually using
template <typename Driver0, typename Driver1> class GxEPD2_Multi
{
public:
void drawPixel(int16_t x, int16_t y, uint16_t color)
{
if (which == 0)
driver0->drawPixel(x, y, color);
else
driver1->drawPixel(x, y, color);
}
bool nextPage()
{
if (which == 0)
return driver0->nextPage();
else
return driver1->nextPage();
}
void hibernate()
{
if (which == 0)
driver0->hibernate();
else
driver1->hibernate();
}
void init(uint32_t serial_diag_bitrate = 0)
{
if (which == 0)
driver0->init(serial_diag_bitrate);
else
driver1->init(serial_diag_bitrate);
}
void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 20, bool pulldown_rst_mode = false)
{
if (which == 0)
driver0->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
else
driver1->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
}
void setRotation(uint8_t x)
{
if (which == 0)
driver0->setRotation(x);
else
driver1->setRotation(x);
}
void setPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
if (which == 0)
driver0->setPartialWindow(x, y, w, h);
else
driver1->setPartialWindow(x, y, w, h);
}
void setFullWindow()
{
if (which == 0)
driver0->setFullWindow();
else
driver1->setFullWindow();
}
int16_t width()
{
if (which == 0)
return driver0->width();
else
return driver1->width();
}
int16_t height()
{
if (which == 0)
return driver0->height();
else
return driver1->height();
}
void clearScreen(uint8_t value = 0xFF)
{
if (which == 0)
driver0->clearScreen();
else
driver1->clearScreen();
}
void endAsyncFull()
{
if (which == 0)
driver0->endAsyncFull();
else
driver1->endAsyncFull();
}
// Exposes methods of the GxEPD2_EPD object which is usually available as GxEPD2_BW::epd
class Epd2Wrapper
{
public:
bool isBusy() { return m_epd2->isBusy(); }
GxEPD2_EPD *m_epd2;
} epd2;
// Constructor
// Select driver by passing whichDriver as 0 or 1
GxEPD2_Multi(uint8_t whichDriver, int16_t cs, int16_t dc, int16_t rst, int16_t busy, SPIClass &spi)
{
assert(whichDriver == 0 || whichDriver == 1);
which = whichDriver;
LOG_DEBUG("GxEPD2_Multi driver: %d", which);
if (which == 0) {
driver0 = new GxEPD2_BW<Driver0, Driver0::HEIGHT>(Driver0(cs, dc, rst, busy, spi));
epd2.m_epd2 = &(driver0->epd2);
} else if (which == 1) {
driver1 = new GxEPD2_BW<Driver1, Driver1::HEIGHT>(Driver1(cs, dc, rst, busy, spi));
epd2.m_epd2 = &(driver1->epd2);
}
}
private:
uint8_t which;
GxEPD2_BW<Driver0, Driver0::HEIGHT> *driver0;
GxEPD2_BW<Driver1, Driver1::HEIGHT> *driver1;
};
+39 -29
View File
@@ -171,7 +171,7 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
} }
// Called to trigger a banner with custom message and duration // Called to trigger a banner with custom message and duration
void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback) void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback)
{ {
#ifdef USE_EINK #ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
@@ -196,7 +196,6 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits,
std::function<void(uint32_t)> bannerCallback) std::function<void(uint32_t)> bannerCallback)
{ {
LOG_WARN("Show Number Picker");
#ifdef USE_EINK #ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
#endif #endif
@@ -294,13 +293,13 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
LOG_INFO("Protobuf Value uiconfig.screen_rgb_color: %d", uiconfig.screen_rgb_color); LOG_INFO("Protobuf Value uiconfig.screen_rgb_color: %d", uiconfig.screen_rgb_color);
int32_t rawRGB = uiconfig.screen_rgb_color; int32_t rawRGB = uiconfig.screen_rgb_color;
if (rawRGB > 0 && rawRGB <= 255255255) { if (rawRGB > 0 && rawRGB <= 255255255) {
uint8_t r = (rawRGB >> 16) & 0xFF; uint8_t TFT_MESH_r = (rawRGB >> 16) & 0xFF;
uint8_t g = (rawRGB >> 8) & 0xFF; uint8_t TFT_MESH_g = (rawRGB >> 8) & 0xFF;
uint8_t b = rawRGB & 0xFF; uint8_t TFT_MESH_b = rawRGB & 0xFF;
LOG_INFO("Values of r,g,b: %d, %d, %d", r, g, b); LOG_INFO("Values of r,g,b: %d, %d, %d", TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
if (r <= 255 && g <= 255 && b <= 255) { if (TFT_MESH_r <= 255 && TFT_MESH_g <= 255 && TFT_MESH_b <= 255) {
TFT_MESH = COLOR565(r, g, b); TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
} }
} }
@@ -313,8 +312,8 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
ST7789_MISO, ST7789_SCK); ST7789_MISO, ST7789_SCK);
#else #else
dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#endif #endif
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#elif defined(USE_SSD1306) #elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry, dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
@@ -944,22 +943,6 @@ void Screen::setFrames(FrameFocus focus)
indicatorIcons.push_back(digital_icon_clock); indicatorIcons.push_back(digital_icon_clock);
#endif #endif
// We don't show the node info of our node (if we have it yet - we should)
size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
if (fsi.positions.firstFavorite == 255)
fsi.positions.firstFavorite = numframes;
fsi.positions.lastFavorite = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawNodeInfo;
indicatorIcons.push_back(icon_node);
}
}
#if HAS_WIFI && !defined(ARCH_PORTDUINO) #if HAS_WIFI && !defined(ARCH_PORTDUINO)
if (!dismissedFrames.wifi && isWifiAvailable()) { if (!dismissedFrames.wifi && isWifiAvailable()) {
fsi.positions.wifi = numframes; fsi.positions.wifi = numframes;
@@ -969,7 +952,7 @@ void Screen::setFrames(FrameFocus focus)
#endif #endif
// Beware of what changes you make in this code! // Beware of what changes you make in this code!
// We pass numfames into GetMeshModulesWithUIFrames() which is highly important! // We pass numframes into GetMeshModulesWithUIFrames() which is highly important!
// Inside of that callback, goes over to MeshModule.cpp and we run // Inside of that callback, goes over to MeshModule.cpp and we run
// modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr // modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr
// entries until we're ready to start building the matching entries. // entries until we're ready to start building the matching entries.
@@ -998,6 +981,34 @@ void Screen::setFrames(FrameFocus focus)
LOG_DEBUG("Added modules. numframes: %d", numframes); LOG_DEBUG("Added modules. numframes: %d", numframes);
// We don't show the node info of our node (if we have it yet - we should)
size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
// Temporary array to hold favorite node frames
std::vector<FrameCallback> favoriteFrames;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
}
}
// Insert favorite frames *after* collecting them all
if (!favoriteFrames.empty()) {
fsi.positions.firstFavorite = numframes;
for (auto &f : favoriteFrames) {
normalFrames[numframes++] = f;
indicatorIcons.push_back(icon_node);
}
fsi.positions.lastFavorite = numframes - 1;
} else {
fsi.positions.firstFavorite = 255;
fsi.positions.lastFavorite = 255;
}
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
this->frameCount = numframes; // ✅ Save frame count for use in custom overlay this->frameCount = numframes; // ✅ Save frame count for use in custom overlay
LOG_DEBUG("Finished build frames. numframes: %d", numframes); LOG_DEBUG("Finished build frames. numframes: %d", numframes);
@@ -1009,8 +1020,7 @@ void Screen::setFrames(FrameFocus focus)
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
// just changed)
// Focus on a specific frame, in the frame set we just created // Focus on a specific frame, in the frame set we just created
switch (focus) { switch (focus) {
@@ -1319,7 +1329,7 @@ int Screen::handleInputEvent(const InputEvent *event)
setFastFramerate(); // Draw ASAP setFastFramerate(); // Draw ASAP
#endif #endif
if (NotificationRenderer::isOverlayBannerShowing()) { if (NotificationRenderer::isOverlayBannerShowing()) {
NotificationRenderer::inEvent = event->inputEvent; NotificationRenderer::inEvent = *event;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP setFastFramerate(); // Draw ASAP
+8 -1
View File
@@ -92,6 +92,7 @@ class Screen
#include "commands.h" #include "commands.h"
#include "concurrency/LockGuard.h" #include "concurrency/LockGuard.h"
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#include "graphics/draw/MenuHandler.h"
#include "input/InputBroker.h" #include "input/InputBroker.h"
#include "mesh/MeshModule.h" #include "mesh/MeshModule.h"
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
@@ -308,9 +309,15 @@ class Screen : public concurrency::OSThread
void showSimpleBanner(const char *message, uint32_t durationMs = 0); void showSimpleBanner(const char *message, uint32_t durationMs = 0);
void showOverlayBanner(BannerOverlayOptions); void showOverlayBanner(BannerOverlayOptions);
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback); void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback); void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
{
graphics::menuHandler::menuQueue = menuToShow;
runNow();
}
void startFirmwareUpdateScreen() void startFirmwareUpdateScreen()
{ {
ScreenCmd cmd; ScreenCmd cmd;
+155 -93
View File
@@ -13,6 +13,7 @@
#include "main.h" #include "main.h"
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h" #include "modules/CannedMessageModule.h"
#include "modules/KeyVerificationModule.h"
extern uint16_t TFT_MESH; extern uint16_t TFT_MESH;
@@ -136,6 +137,7 @@ void menuHandler::ClockFacePicker()
screen->setFrames(Screen::FOCUS_CLOCK); screen->setFrames(Screen::FOCUS_CLOCK);
} }
}; };
bannerOptions.InitialSelected = uiconfig.is_clockface_analog ? 2 : 1;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
@@ -236,27 +238,25 @@ void menuHandler::clockMenu()
void menuHandler::messageResponseMenu() void menuHandler::messageResponseMenu()
{ {
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
int options = 3;
static const char **optionsArrayPtr;
int options;
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3 };
if (kb_found) { if (kb_found) {
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext"}; optionsArray[options] = "Reply via Freetext";
optionsArrayPtr = optionsArray; optionsEnumArray[options++] = Freetext;
options = 4;
} else {
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset"};
optionsArrayPtr = optionsArray;
options = 3;
} }
#ifdef HAS_I2S #ifdef HAS_I2S
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext", "Read Aloud"}; optionsArray[options] = "Read Aloud";
optionsArrayPtr = optionsArray; optionsEnumArray[options++] = Aloud;
options = 5;
#endif #endif
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action"; bannerOptions.message = "Message Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dismiss) { if (selected == Dismiss) {
@@ -275,7 +275,7 @@ void menuHandler::messageResponseMenu()
} }
} }
#ifdef HAS_I2S #ifdef HAS_I2S
else if (selected == 4) { else if (selected == Aloud) {
const meshtastic_MeshPacket &mp = devicestate.rx_text_message; const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes); const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes);
@@ -288,10 +288,10 @@ void menuHandler::messageResponseMenu()
void menuHandler::homeBaseMenu() void menuHandler::homeBaseMenu()
{ {
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep }; enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep, enumEnd };
static const char *optionsArray[6] = {"Back"}; static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[6] = {Back}; static int optionsEnumArray[enumEnd] = {Back};
int options = 1; int options = 1;
#ifdef PIN_EINK_EN #ifdef PIN_EINK_EN
@@ -337,8 +337,8 @@ void menuHandler::homeBaseMenu()
} else if (selected == Freetext) { } else if (selected == Freetext) {
cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST); cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST);
} else if (selected == Bluetooth) { } else if (selected == Bluetooth) {
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0}; menuQueue = bluetooth_toggle_menu;
inputBroker->injectInputEvent(&event); screen->runNow();
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@@ -353,11 +353,14 @@ void menuHandler::systemBaseMenu()
hasSupportBrightness = true; hasSupportBrightness = true;
#endif #endif
enum optionsNumbers { Back, Beeps, Brightness, Reboot, Color, MUI, Test }; enum optionsNumbers { Back, Beeps, Brightness, Reboot, Color, MUI, Test, enumEnd };
static const char *optionsArray[7] = {"Back"}; static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[7] = {Back}; static int optionsEnumArray[enumEnd] = {Back};
int options = 1; int options = 1;
optionsArray[options] = "Reboot";
optionsEnumArray[options++] = Reboot;
optionsArray[options] = "Beeps Action"; optionsArray[options] = "Beeps Action";
optionsEnumArray[options++] = Beeps; optionsEnumArray[options++] = Beeps;
@@ -366,9 +369,6 @@ void menuHandler::systemBaseMenu()
optionsEnumArray[options++] = Brightness; optionsEnumArray[options++] = Brightness;
} }
optionsArray[options] = "Reboot";
optionsEnumArray[options++] = Reboot;
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Color"; optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = Color; optionsEnumArray[options++] = Color;
@@ -418,21 +418,22 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu() void menuHandler::favoriteBaseMenu()
{ {
int options; enum optionsNumbers { Back, Preset, Freetext, Remove, enumEnd };
static const char **optionsArrayPtr; static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2;
if (kb_found) { if (kb_found) {
static const char *optionsArray[] = {"Back", "New Preset Msg", "New Freetext Msg", "Remove Favorite"}; optionsArray[options] = "New Freetext Msg";
optionsArrayPtr = optionsArray; optionsEnumArray[options++] = Freetext;
options = 4;
} else {
static const char *optionsArray[] = {"Back", "New Preset Msg", "Remove Favorite"};
optionsArrayPtr = optionsArray;
options = 3;
} }
optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Favorites Action"; bannerOptions.message = "Favorites Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == 1) {
@@ -449,34 +450,29 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu() void menuHandler::positionBaseMenu()
{ {
int options; enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd };
static const char **optionsArrayPtr;
static const char *optionsArray[] = {"Back", "GPS Toggle", "Compass"}; static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"};
static const char *optionsArrayCalibrate[] = {"Back", "GPS Toggle", "Compass", "Compass Calibrate"}; static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
int options = 3;
if (accelerometerThread) { if (accelerometerThread) {
optionsArrayPtr = optionsArrayCalibrate; optionsArray[options] = "Compass Calibrate";
options = 4; optionsEnumArray[options++] = CompassCalibrate;
} else {
optionsArrayPtr = optionsArray;
options = 3;
} }
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action"; bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == GPSToggle) {
#if MESHTASTIC_EXCLUDE_GPS
menuQueue = menu_none;
#else
menuQueue = gps_toggle_menu; menuQueue = gps_toggle_menu;
screen->runNow(); screen->runNow();
#endif } else if (selected == CompassMenu) {
} else if (selected == 2) {
menuQueue = compass_point_north_menu; menuQueue = compass_point_north_menu;
screen->runNow(); screen->runNow();
} else if (selected == 3) { } else if (selected == CompassCalibrate) {
accelerometerThread->calibrate(30); accelerometerThread->calibrate(30);
} }
}; };
@@ -485,16 +481,20 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu() void menuHandler::nodeListMenu()
{ {
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset NodeDB"}; enum optionsNumbers { Back, Favorite, Verify, Reset };
static const char *optionsArray[] = {"Back", "Add Favorite", "Key Verification", "Reset NodeDB"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action"; bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3; bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == Favorite) {
menuQueue = add_favorite; menuQueue = add_favorite;
screen->runNow(); screen->runNow();
} else if (selected == 2) { } else if (selected == Verify) {
menuQueue = key_verification_init;
screen->runNow();
} else if (selected == Reset) {
menuQueue = reset_node_db_menu; menuQueue = reset_node_db_menu;
screen->runNow(); screen->runNow();
} }
@@ -522,6 +522,7 @@ void menuHandler::resetNodeDBMenu()
void menuHandler::compassNorthMenu() void menuHandler::compassNorthMenu()
{ {
enum optionsNumbers { Back, Dynamic, Fixed, Freeze };
static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"}; static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "North Directions?"; bannerOptions.message = "North Directions?";
@@ -529,28 +530,28 @@ void menuHandler::compassNorthMenu()
bannerOptions.optionsCount = 4; bannerOptions.optionsCount = 4;
bannerOptions.InitialSelected = uiconfig.compass_mode + 1; bannerOptions.InitialSelected = uiconfig.compass_mode + 1;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == Dynamic) {
if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) { if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) {
uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC; uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig); &uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
} else if (selected == 2) { } else if (selected == Fixed) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) { if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING; uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig); &uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
} else if (selected == 3) { } else if (selected == Freeze) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING; uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig); &uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
} else if (selected == 0) { } else if (selected == Back) {
menuQueue = position_base_menu; menuQueue = position_base_menu;
screen->runNow(); screen->runNow();
} }
@@ -561,6 +562,7 @@ void menuHandler::compassNorthMenu()
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
void menuHandler::GPSToggleMenu() void menuHandler::GPSToggleMenu()
{ {
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle GPS"; bannerOptions.message = "Toggle GPS";
@@ -587,6 +589,23 @@ void menuHandler::GPSToggleMenu()
} }
#endif #endif
void menuHandler::BluetoothToggleMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle Bluetooth";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1 || selected == 2) {
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
}
};
bannerOptions.InitialSelected = config.bluetooth.enabled ? 1 : 2;
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::BuzzerModeMenu() void menuHandler::BuzzerModeMenu()
{ {
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"}; static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"};
@@ -677,52 +696,52 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 10; bannerOptions.optionsCount = 10;
bannerOptions.bannerCallback = [display](int selected) -> void { bannerOptions.bannerCallback = [display](int selected) -> void {
uint8_t r = 0; uint8_t TFT_MESH_r = 0;
uint8_t g = 0; uint8_t TFT_MESH_g = 0;
uint8_t b = 0; uint8_t TFT_MESH_b = 0;
if (selected == 1) { if (selected == 1) {
LOG_INFO("Setting color to system default or defined variant"); LOG_INFO("Setting color to system default or defined variant");
// Given just before we set all these to zero, we will allow this to go through // Given just before we set all these to zero, we will allow this to go through
} else if (selected == 2) { } else if (selected == 2) {
LOG_INFO("Setting color to Meshtastic Green"); LOG_INFO("Setting color to Meshtastic Green");
r = 103; TFT_MESH_r = 103;
g = 234; TFT_MESH_g = 234;
b = 148; TFT_MESH_b = 148;
} else if (selected == 3) { } else if (selected == 3) {
LOG_INFO("Setting color to Yellow"); LOG_INFO("Setting color to Yellow");
r = 255; TFT_MESH_r = 255;
g = 255; TFT_MESH_g = 255;
b = 128; TFT_MESH_b = 128;
} else if (selected == 4) { } else if (selected == 4) {
LOG_INFO("Setting color to Red"); LOG_INFO("Setting color to Red");
r = 255; TFT_MESH_r = 255;
g = 64; TFT_MESH_g = 64;
b = 64; TFT_MESH_b = 64;
} else if (selected == 5) { } else if (selected == 5) {
LOG_INFO("Setting color to Orange"); LOG_INFO("Setting color to Orange");
r = 255; TFT_MESH_r = 255;
g = 160; TFT_MESH_g = 160;
b = 20; TFT_MESH_b = 20;
} else if (selected == 6) { } else if (selected == 6) {
LOG_INFO("Setting color to Purple"); LOG_INFO("Setting color to Purple");
r = 204; TFT_MESH_r = 204;
g = 153; TFT_MESH_g = 153;
b = 255; TFT_MESH_b = 255;
} else if (selected == 7) { } else if (selected == 7) {
LOG_INFO("Setting color to Teal"); LOG_INFO("Setting color to Teal");
r = 64; TFT_MESH_r = 64;
g = 224; TFT_MESH_g = 224;
b = 208; TFT_MESH_b = 208;
} else if (selected == 8) { } else if (selected == 8) {
LOG_INFO("Setting color to Pink"); LOG_INFO("Setting color to Pink");
r = 255; TFT_MESH_r = 255;
g = 105; TFT_MESH_g = 105;
b = 180; TFT_MESH_b = 180;
} else if (selected == 9) { } else if (selected == 9) {
LOG_INFO("Setting color to White"); LOG_INFO("Setting color to White");
r = 255; TFT_MESH_r = 255;
g = 255; TFT_MESH_g = 255;
b = 255; TFT_MESH_b = 255;
} }
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
@@ -731,14 +750,14 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
display->setColor(WHITE); display->setColor(WHITE);
if (r == 0 && g == 0 && b == 0) { if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
#ifdef TFT_MESH_OVERRIDE #ifdef TFT_MESH_OVERRIDE
TFT_MESH = TFT_MESH_OVERRIDE; TFT_MESH = TFT_MESH_OVERRIDE;
#else #else
TFT_MESH = COLOR565(0x67, 0xEA, 0x94); TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
#endif #endif
} else { } else {
TFT_MESH = COLOR565(r, g, b); TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
} }
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
@@ -746,10 +765,10 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
#endif #endif
screen->setFrames(graphics::Screen::FOCUS_SYSTEM); screen->setFrames(graphics::Screen::FOCUS_SYSTEM);
if (r == 0 && g == 0 && b == 0) { if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
uiconfig.screen_rgb_color = 0; uiconfig.screen_rgb_color = 0;
} else { } else {
uiconfig.screen_rgb_color = (r << 16) | (g << 8) | b; uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b;
} }
LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color); LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color);
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
@@ -778,7 +797,7 @@ void menuHandler::rebootMenu()
void menuHandler::addFavoriteMenu() void menuHandler::addFavoriteMenu()
{ {
screen->showNodePicker("Node To Favorite", 30000, [](int nodenum) -> void { screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
LOG_WARN("Nodenum: %u", nodenum); LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum); nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
@@ -869,6 +888,37 @@ void menuHandler::wifiToggleMenu()
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::keyVerificationInitMenu()
{
screen->showNodePicker("Node to Verify", 30000,
[](uint32_t selected) -> void { keyVerificationModule->sendInitialRequest(selected); });
}
void menuHandler::keyVerificationFinalPrompt()
{
char message[40] = {0};
memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n");
keyVerificationModule->generateVerificationCode(message + 15); // send the toPhone packet
if (screen) {
static const char *optionsArray[] = {"Reject", "Accept"};
graphics::BannerOverlayOptions options;
options.message = message;
options.durationMs = 30000;
options.optionsArrayPtr = optionsArray;
options.optionsCount = 2;
options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.bannerCallback = [=](int selected) {
if (selected == 1) {
auto remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode());
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
};
screen->showOverlayBanner(options);
}
}
void menuHandler::handleMenuSwitch(OLEDDisplay *display) void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{ {
if (menuQueue != menu_none) if (menuQueue != menu_none)
@@ -935,6 +985,18 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case wifi_toggle_menu: case wifi_toggle_menu:
wifiToggleMenu(); wifiToggleMenu();
break; break;
case key_verification_init:
keyVerificationInitMenu();
break;
case key_verification_final_prompt:
keyVerificationFinalPrompt();
break;
case bluetooth_toggle_menu:
BluetoothToggleMenu();
break;
case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break;
} }
menuQueue = menu_none; menuQueue = menu_none;
} }
+12 -2
View File
@@ -1,3 +1,5 @@
#pragma once
#if HAS_SCREEN
#include "configuration.h" #include "configuration.h"
namespace graphics namespace graphics
{ {
@@ -25,7 +27,11 @@ class menuHandler
remove_favorite, remove_favorite,
test_menu, test_menu,
number_test, number_test,
wifi_toggle_menu wifi_toggle_menu,
key_verification_init,
key_verification_final_prompt,
bluetooth_toggle_menu,
throttle_message
}; };
static screenMenus menuQueue; static screenMenus menuQueue;
@@ -55,6 +61,10 @@ class menuHandler
static void numberTest(); static void numberTest();
static void wifiBaseMenu(); static void wifiBaseMenu();
static void wifiToggleMenu(); static void wifiToggleMenu();
static void keyVerificationInitMenu();
static void keyVerificationFinalPrompt();
static void BluetoothToggleMenu();
}; };
} // namespace graphics } // namespace graphics
#endif
+57 -24
View File
@@ -26,7 +26,7 @@ extern bool hasUnreadMessage;
namespace graphics namespace graphics
{ {
char NotificationRenderer::inEvent = INPUT_BROKER_NONE; InputEvent NotificationRenderer::inEvent;
int8_t NotificationRenderer::curSelected = 0; int8_t NotificationRenderer::curSelected = 0;
char NotificationRenderer::alertBannerMessage[256] = {0}; char NotificationRenderer::alertBannerMessage[256] = {0};
uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever
@@ -42,7 +42,7 @@ uint32_t NotificationRenderer::currentNumber = 0;
uint32_t pow_of_10(uint32_t n) uint32_t pow_of_10(uint32_t n)
{ {
uint32_t ret = 1; uint32_t ret = 1;
for (int i = 0; i < n; i++) { for (uint32_t i = 0; i < n; i++) {
ret *= 10; ret *= 10;
} }
return ret; return ret;
@@ -72,14 +72,31 @@ void NotificationRenderer::resetBanner()
{ {
alertBannerMessage[0] = '\0'; alertBannerMessage[0] = '\0';
current_notification_type = notificationTypeEnum::none; current_notification_type = notificationTypeEnum::none;
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent.kbchar = 0;
curSelected = 0;
alertBannerOptions = 0; // last x lines are seelctable options
optionsArrayPtr = nullptr;
optionsEnumPtr = nullptr;
alertBannerCallback = NULL;
pauseBanner = false;
numDigits = 0;
currentNumber = 0;
nodeDB->pause_sort(false); nodeDB->pause_sort(false);
} }
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state) void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
{ {
if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0')
resetBanner();
if (!isOverlayBannerShowing() || pauseBanner) if (!isOverlayBannerShowing() || pauseBanner)
return; return;
switch (current_notification_type) { switch (current_notification_type) {
case notificationTypeEnum::none:
// Do nothing - no notification to display
break;
case notificationTypeEnum::text_banner: case notificationTypeEnum::text_banner:
case notificationTypeEnum::selection_picker: case notificationTypeEnum::selection_picker:
drawAlertBannerOverlay(display, state); drawAlertBannerOverlay(display, state);
@@ -112,31 +129,40 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
// modulo to extract // modulo to extract
uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1)); uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1));
// Handle input // Handle input
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (this_digit == 9) { if (this_digit == 9) {
currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1)); currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1));
} else { } else {
currentNumber += (pow_of_10(numDigits - curSelected - 1)); currentNumber += (pow_of_10(numDigits - curSelected - 1));
} }
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
if (this_digit == 0) { if (this_digit == 0) {
currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1)); currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1));
} else { } else {
currentNumber -= (pow_of_10(numDigits - curSelected - 1)); currentNumber -= (pow_of_10(numDigits - curSelected - 1));
} }
} else if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_RIGHT) { } else if (inEvent.inputEvent == INPUT_BROKER_ANYKEY) {
if (inEvent.kbchar > 47 && inEvent.kbchar < 58) { // have a digit
currentNumber -= this_digit * (pow_of_10(numDigits - curSelected - 1));
currentNumber += (inEvent.kbchar - 48) * (pow_of_10(numDigits - curSelected - 1));
curSelected++;
}
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_RIGHT) {
curSelected++; curSelected++;
} else if (inEvent == INPUT_BROKER_LEFT) { } else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
curSelected--; curSelected--;
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) { } else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner(); resetBanner();
return;
} }
if (curSelected == numDigits) { if (curSelected == numDigits) {
resetBanner();
alertBannerCallback(currentNumber); alertBannerCallback(currentNumber);
resetBanner();
return;
} }
inEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0') if (alertBannerMessage[0] == '\0')
return; return;
@@ -144,12 +170,12 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
const char *linePointers[totalLines + 1] = {0}; // this is sort of a dynamic allocation const char *linePointers[totalLines + 1] = {0}; // this is sort of a dynamic allocation
// copy the linestarts to display to the linePointers holder // copy the linestarts to display to the linePointers holder
for (int i = 0; i < lineCount; i++) { for (uint16_t i = 0; i < lineCount; i++) {
linePointers[i] = lineStarts[i]; linePointers[i] = lineStarts[i];
} }
std::string digits = " "; std::string digits = " ";
std::string arrowPointer = " "; std::string arrowPointer = " ";
for (int i = 0; i < numDigits; i++) { for (uint16_t i = 0; i < numDigits; i++) {
// Modulo minus modulo to return just the current number // Modulo minus modulo to return just the current number
digits += std::to_string((currentNumber % (pow_of_10(numDigits - i))) / (pow_of_10(numDigits - i - 1))) + " "; digits += std::to_string((currentNumber % (pow_of_10(numDigits - i))) / (pow_of_10(numDigits - i - 1))) + " ";
if (curSelected == i) { if (curSelected == i) {
@@ -190,16 +216,18 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
} }
// Handle input // Handle input
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--; curSelected--;
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++; curSelected++;
} else if (inEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
resetBanner();
alertBannerCallback(selectedNodenum); alertBannerCallback(selectedNodenum);
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
resetBanner(); resetBanner();
return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner();
return;
} }
if (curSelected == -1) if (curSelected == -1)
@@ -207,7 +235,7 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
if (curSelected == alertBannerOptions) if (curSelected == alertBannerOptions)
curSelected = 0; curSelected = 0;
inEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0') if (alertBannerMessage[0] == '\0')
return; return;
@@ -305,11 +333,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
// Handle input // Handle input
if (alertBannerOptions > 0) { if (alertBannerOptions > 0) {
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--; curSelected--;
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++; curSelected++;
} else if (inEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
if (optionsEnumPtr != nullptr) { if (optionsEnumPtr != nullptr) {
alertBannerCallback(optionsEnumPtr[curSelected]); alertBannerCallback(optionsEnumPtr[curSelected]);
optionsEnumPtr = nullptr; optionsEnumPtr = nullptr;
@@ -317,8 +345,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
alertBannerCallback(curSelected); alertBannerCallback(curSelected);
} }
resetBanner(); resetBanner();
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) { return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner(); resetBanner();
return;
} }
if (curSelected == -1) if (curSelected == -1)
@@ -326,12 +357,14 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
if (curSelected == alertBannerOptions) if (curSelected == alertBannerOptions)
curSelected = 0; curSelected = 0;
} else { } else {
if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_ALT_LONG || inEvent == INPUT_BROKER_CANCEL) { if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_ALT_LONG ||
inEvent.inputEvent == INPUT_BROKER_CANCEL) {
resetBanner(); resetBanner();
return;
} }
} }
inEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0') if (alertBannerMessage[0] == '\0')
return; return;
+2 -1
View File
@@ -11,7 +11,8 @@ namespace graphics
class NotificationRenderer class NotificationRenderer
{ {
public: public:
static char inEvent; static InputEvent inEvent;
static char inKeypress;
static int8_t curSelected; static int8_t curSelected;
static char alertBannerMessage[256]; static char alertBannerMessage[256];
static uint32_t alertBannerUntil; // 0 is a special case meaning forever static uint32_t alertBannerUntil; // 0 is a special case meaning forever
@@ -0,0 +1,84 @@
#include "./E0213A367.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void E0213A367::configScanning()
{
// "Driver output control"
// Scan gates from 0 to 249 (vertical resolution 250px)
sendCommand(0x01);
sendData(0xF9);
sendData(0x00);
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
void E0213A367::configWaveform()
{
// This command (0x37) is poorly documented
// As of July 2025, the datasheet for this display's controller IC is unavailable
// The values are supplied by Heltec, who presumably have privileged access to information from the display manufacturer
// Datasheet for the similar SSD1680 IC hints at the function of this command:
// "Spare VCOM OTP selection":
// Unclear why 0x40 is set. Sane values for related SSD1680 seem to be 0x80 or 0x00.
// Maybe value is redundant? No noticeable impact when set to 0x00.
// We'll leave it set to 0x40, following Heltec's lead, just in case.
// "Display Mode"
// Seems to specify whether a waveform stored in OTP should use display mode 1 or 2 (full refresh or differential refresh)
// Unusual that waveforms are programmed to OTP, but this meta information is not ..?
sendCommand(0x37); // "Write Register for Display Option" ?
sendData(0x40); // "Spare VCOM OTP selection" ?
sendData(0x80); // "Display Mode for WS[7:0]" ?
sendData(0x03); // "Display Mode for WS[15:8]" ?
sendData(0x0E); // "Display Mode [23:16]" ?
switch (updateType) {
case FAST:
sendCommand(0x3C); // Border waveform:
sendData(0x81); // As specified by Heltec. Actually VCOM (0x80)?. Bit 0 seems redundant here.
break;
case FULL:
default:
sendCommand(0x3C); // Border waveform:
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
break;
}
}
// Tell controller IC which operations to run
void E0213A367::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory, Display mode 1 "full refresh"
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void E0213A367::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 500); // At least 500ms for fast refresh
case FULL:
default:
return beginPolling(100, 1500); // At least 1.5 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,41 @@
/*
E-Ink display driver
- SSD1682
- Manufacturer: SEEKINK
- Size: 2.13 inch
- Resolution: 122px x 255px
- Flex connector marking: HINK-E0213A162-A1 (hidden, printed on reverse)
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD1682.h"
namespace NicheGraphics::Drivers
{
class E0213A367 : public SSD1682
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
E0213A367() : SSD1682(width, height, supported, 0) {}
protected:
void configScanning() override;
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,41 @@
#include "./SSD1682.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
SSD1682::SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX)
: SSD16XX(width, height, supported, bufferOffsetX)
{
}
// SSD1682 only accepts single-byte x and y values
// This causes an incompatibility with the default SSD16XX::configFullscreen
void SSD1682::configFullscreen()
{
// Define the boundaries of the "fullscreen" region, for the controller IC
static const uint8_t sx = bufferOffsetX; // Notice the offset
static const uint8_t sy = 0;
static const uint8_t ex = bufferRowSize + bufferOffsetX - 1; // End is "max index", not "count". Minus 1 handles this
static const uint8_t ey = height;
// Data entry mode - Left to Right, Top to Bottom
sendCommand(0x11);
sendData(0x03);
// Select controller IC memory region to display a fullscreen image
sendCommand(0x44); // Memory X start - end
sendData(sx);
sendData(ex);
sendCommand(0x45); // Memory Y start - end
sendData(sy);
sendData(ey);
// Place the cursor at the start of this memory region, ready to send image data x=0 y=0
sendCommand(0x4E); // Memory cursor X
sendData(sx);
sendCommand(0x4F); // Memory cursor y
sendData(sy);
}
#endif
+31
View File
@@ -0,0 +1,31 @@
/*
E-Ink base class for displays based on SSD1682
SSD1682 has a few quirks. We're implementing them here in a new base class,
to avoid re-implementing them every time we need to add a new SSD1682-based display.
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class SSD1682 : public SSD16XX
{
public:
SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX = 0);
virtual void configFullscreen(); // Select memory region on controller IC
virtual void deepSleep() {} // Not usable (image memory not retained)
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+8
View File
@@ -369,6 +369,14 @@ NodeDB::NodeDB()
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY; config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY;
} }
#if !HAS_TFT
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
// On a device without MUI, this display mode makes no sense, and will break logic.
config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
config.bluetooth.enabled = true;
}
#endif
if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate))) if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate)))
saveWhat |= SEGMENT_DEVICESTATE; saveWhat |= SEGMENT_DEVICESTATE;
if (nodeDatabaseCRC != crc32Buffer(&nodeDatabase, sizeof(nodeDatabase))) if (nodeDatabaseCRC != crc32Buffer(&nodeDatabase, sizeof(nodeDatabase)))
+4
View File
@@ -246,8 +246,10 @@ void PacketHistory::insert(PacketRecord &r)
#if RECENT_WARN_AGE > 0 #if RECENT_WARN_AGE > 0
if (tu->rxTimeMsec && (OldtrxTimeMsec < RECENT_WARN_AGE)) { if (tu->rxTimeMsec && (OldtrxTimeMsec < RECENT_WARN_AGE)) {
if (!(tu->id == r.id && tu->sender == r.sender)) { if (!(tu->id == r.id && tu->sender == r.sender)) {
#if VERBOSE_PACKET_HISTORY
LOG_WARN("Packet History - insert: Reusing slot aged %ds < %ds RECENT_WARN_AGE", OldtrxTimeMsec / 1000, LOG_WARN("Packet History - insert: Reusing slot aged %ds < %ds RECENT_WARN_AGE", OldtrxTimeMsec / 1000,
RECENT_WARN_AGE / 1000); RECENT_WARN_AGE / 1000);
#endif
} else { } else {
// debug only // debug only
#if VERBOSE_PACKET_HISTORY #if VERBOSE_PACKET_HISTORY
@@ -275,7 +277,9 @@ void PacketHistory::insert(PacketRecord &r)
#endif #endif
if (r.rxTimeMsec == 0) { if (r.rxTimeMsec == 0) {
#if VERBOSE_PACKET_HISTORY
LOG_WARN("Packet History - insert: I will not store packet with rxTimeMsec = 0."); LOG_WARN("Packet History - insert: I will not store packet with rxTimeMsec = 0.");
#endif
return; // Return early if we can't update the history return; // Return early if we can't update the history
} }
-4
View File
@@ -645,10 +645,6 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
if (power > loraMaxPower) // Clamp power to maximum defined level if (power > loraMaxPower) // Clamp power to maximum defined level
power = loraMaxPower; power = loraMaxPower;
if (TX_GAIN_LORA == 0) { // Setting power in config with defined TX_GAIN_LORA will cause decreasing power on each reboot
config.lora.tx_power = power; // Set limited power in config
}
LOG_INFO("Final Tx power: %d dBm", power); LOG_INFO("Final Tx power: %d dBm", power);
} }
+3 -2
View File
@@ -13,8 +13,9 @@ template <class T> constexpr const T &clamp(const T &v, const T &lo, const T &hi
#if HAS_SCREEN #if HAS_SCREEN
#define IF_SCREEN(X) \ #define IF_SCREEN(X) \
if (screen) \ if (screen) { \
X; X; \
}
#else #else
#define IF_SCREEN(...) #define IF_SCREEN(...)
#endif #endif
-6
View File
@@ -1327,12 +1327,6 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent
LOG_DEBUG("Processing input event: event_code=%u, kb_char=%u, touch_x=%u, touch_y=%u", inputEvent.event_code, LOG_DEBUG("Processing input event: event_code=%u, kb_char=%u, touch_x=%u, touch_y=%u", inputEvent.event_code,
inputEvent.kb_char, inputEvent.touch_x, inputEvent.touch_y); inputEvent.kb_char, inputEvent.touch_x, inputEvent.touch_y);
// Validate input parameters
if (inputEvent.event_code > INPUT_BROKER_ANYKEY) {
LOG_WARN("Invalid input event code: %u", inputEvent.event_code);
return;
}
// Create InputEvent for injection // Create InputEvent for injection
InputEvent event = {.inputEvent = (input_broker_event)inputEvent.event_code, InputEvent event = {.inputEvent = (input_broker_event)inputEvent.event_code,
.kbchar = (unsigned char)inputEvent.kb_char, .kbchar = (unsigned char)inputEvent.kb_char,
+4 -2
View File
@@ -716,7 +716,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
} }
// Backspace // Backspace
if (event->inputEvent == INPUT_BROKER_BACK) { if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) {
payload = 0x08; payload = 0x08;
lastTouchMillis = millis(); lastTouchMillis = millis();
runOnce(); runOnce();
@@ -739,7 +739,8 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
} }
// Cancel (dismiss freetext screen) // Cancel (dismiss freetext screen)
if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG) { if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG ||
(event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() == 0)) {
runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
freetext = ""; freetext = "";
cursor = 0; cursor = 0;
@@ -989,6 +990,7 @@ int32_t CannedMessageModule::runOnce()
} }
this->cursor--; this->cursor--;
} }
} else {
} }
break; break;
case INPUT_BROKER_MSG_TAB: // Tab key: handled by input handler case INPUT_BROKER_MSG_TAB: // Tab key: handled by input handler
+28 -34
View File
@@ -2,7 +2,9 @@
#include "KeyVerificationModule.h" #include "KeyVerificationModule.h"
#include "MeshService.h" #include "MeshService.h"
#include "RTC.h" #include "RTC.h"
#include "graphics/draw/MenuHandler.h"
#include "main.h" #include "main.h"
#include "meshUtils.h"
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
#include <SHA256.h> #include <SHA256.h>
@@ -48,18 +50,22 @@ AdminMessageHandleResult KeyVerificationModule::handleAdminMessageForModule(cons
bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_KeyVerification *r) bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_KeyVerification *r)
{ {
updateState(); updateState();
if (mp.pki_encrypted == false) if (mp.pki_encrypted == false) {
return false; return false;
if (mp.from != currentRemoteNode) // because the inital connection request is handled in allocReply() }
if (mp.from != currentRemoteNode) { // because the inital connection request is handled in allocReply()
return false; return false;
}
if (currentState == KEY_VERIFICATION_IDLE) { if (currentState == KEY_VERIFICATION_IDLE) {
return false; // if we're idle, the only acceptable message is an init, which should be handled by allocReply() return false; // if we're idle, the only acceptable message is an init, which should be handled by allocReply()
}
} else if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 && if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 &&
r->hash1.size == 0) { r->hash1.size == 0) {
memcpy(hash2, r->hash2.bytes, 32); memcpy(hash2, r->hash2.bytes, 32);
if (screen) IF_SCREEN(screen->showNumberPicker("Enter Security Number", 60000, 6, [](int number_picked) -> void {
screen->showSimpleBanner("Enter Security Number", 30000); keyVerificationModule->processSecurityNumber(number_picked);
});)
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING; cn->level = meshtastic_LogRecord_Level_WARNING;
@@ -79,23 +85,18 @@ bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
memset(message, 0, sizeof(message)); memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n"); sprintf(message, "Verification: \n");
generateVerificationCode(message + 15); generateVerificationCode(message + 15);
static const char *optionsArray[] = {"ACCEPT", "REJECT"};
LOG_INFO("Hash1 matches!"); LOG_INFO("Hash1 matches!");
if (screen) { IF_SCREEN(static const char *optionsArray[] = {"Reject", "Accept"}; graphics::BannerOverlayOptions options;
graphics::BannerOverlayOptions options; options.message = message; options.durationMs = 30000; options.optionsArrayPtr = optionsArray;
options.message = message; options.optionsCount = 2; options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.durationMs = 30000; options.bannerCallback =
options.optionsArrayPtr = optionsArray; [=](int selected) {
options.optionsCount = 2; if (selected == 1) {
options.notificationType = graphics::notificationTypeEnum::selection_picker; auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
options.bannerCallback = [=](int selected) { remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
if (selected == 0) { }
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode); };
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK; screen->showOverlayBanner(options);)
}
};
screen->showOverlayBanner(options);
}
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING; cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Final confirmation for incoming manual key verification %s", message); sprintf(cn->message, "Final confirmation for incoming manual key verification %s", message);
@@ -120,6 +121,7 @@ bool KeyVerificationModule::sendInitialRequest(NodeNum remoteNode)
// generate nonce // generate nonce
updateState(); updateState();
if (currentState != KEY_VERIFICATION_IDLE) { if (currentState != KEY_VERIFICATION_IDLE) {
IF_SCREEN(graphics::menuHandler::menuQueue = graphics::menuHandler::throttle_message;)
return false; return false;
} }
currentNonce = random(); currentNonce = random();
@@ -190,11 +192,8 @@ meshtastic_MeshPacket *KeyVerificationModule::allocReply()
responsePacket = allocDataProtobuf(response); responsePacket = allocDataProtobuf(response);
responsePacket->pki_encrypted = true; responsePacket->pki_encrypted = true;
if (screen) { IF_SCREEN(snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000);
snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000); screen->showSimpleBanner(message, 30000); LOG_WARN("%s", message);)
screen->showSimpleBanner(message, 30000);
LOG_WARN("%s", message);
}
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING; cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Incoming Key Verification.\nSecurity Number\n%03u %03u", currentSecurityNumber / 1000, sprintf(cn->message, "Incoming Key Verification.\nSecurity Number\n%03u %03u", currentSecurityNumber / 1000,
@@ -258,12 +257,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
p->priority = meshtastic_MeshPacket_Priority_HIGH; p->priority = meshtastic_MeshPacket_Priority_HIGH;
service->sendToMesh(p, RX_SRC_LOCAL, true); service->sendToMesh(p, RX_SRC_LOCAL, true);
currentState = KEY_VERIFICATION_SENDER_AWAITING_USER; currentState = KEY_VERIFICATION_SENDER_AWAITING_USER;
memset(message, 0, sizeof(message)); IF_SCREEN(screen->requestMenu(graphics::menuHandler::key_verification_final_prompt);)
sprintf(message, "Verification: \n");
generateVerificationCode(message + 15); // send the toPhone packet
if (screen) {
screen->showSimpleBanner(message, 30000);
}
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING; cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Final confirmation for outgoing manual key verification %s", message); sprintf(cn->message, "Final confirmation for outgoing manual key verification %s", message);
@@ -282,7 +276,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
void KeyVerificationModule::updateState() void KeyVerificationModule::updateState()
{ {
if (currentState != KEY_VERIFICATION_IDLE) { if (currentState != KEY_VERIFICATION_IDLE) {
// check for the 30 second timeout // check for the 60 second timeout
if (currentNonceTimestamp < getTime() - 60) { if (currentNonceTimestamp < getTime() - 60) {
resetToIdle(); resetToIdle();
} else { } else {
+4 -3
View File
@@ -27,6 +27,8 @@ class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification>
}*/ }*/
virtual bool wantUIFrame() { return false; }; virtual bool wantUIFrame() { return false; };
bool sendInitialRequest(NodeNum remoteNode); bool sendInitialRequest(NodeNum remoteNode);
void generateVerificationCode(char *); // fills char with the user readable verification code
uint32_t getCurrentRemoteNode() { return currentRemoteNode; }
protected: protected:
/* Called to handle a particular incoming message /* Called to handle a particular incoming message
@@ -56,9 +58,8 @@ class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification>
char message[40] = {0}; char message[40] = {0};
void processSecurityNumber(uint32_t); void processSecurityNumber(uint32_t);
void updateState(); // check the timeouts and maybe reset the state to idle void updateState(); // check the timeouts and maybe reset the state to idle
void resetToIdle(); // Zero out module state void resetToIdle(); // Zero out module state
void generateVerificationCode(char *); // fills char with the user readable verification code
}; };
extern KeyVerificationModule *keyVerificationModule; extern KeyVerificationModule *keyVerificationModule;
@@ -0,0 +1,35 @@
#pragma once
#include "configuration.h"
enum class EInkDetectionResult : uint8_t {
LCMEN213EFC1 = 0, // Initial version
E0213A367 = 1, // E213 PCB marked V1.1 (Mid 2025)
};
EInkDetectionResult detectEInk()
{
// Test 1: Logic of BUSY pin
// Determines controller IC manufacturer
// Fitipower: busy when LOW
// Solomon Systech: busy when HIGH
// Force display BUSY by holding reset pin active
pinMode(PIN_EINK_RES, OUTPUT);
digitalWrite(PIN_EINK_RES, LOW);
delay(10);
// Read whether pin is HIGH or LOW while busy
pinMode(PIN_EINK_BUSY, INPUT);
bool busyLogic = digitalRead(PIN_EINK_BUSY);
// Test complete. Release pin
pinMode(PIN_EINK_RES, INPUT);
if (busyLogic == LOW)
return EInkDetectionResult::LCMEN213EFC1;
else // busy HIGH
return EInkDetectionResult::E0213A367;
}
@@ -18,16 +18,22 @@
// Shared NicheGraphics components // Shared NicheGraphics components
// -------------------------------- // --------------------------------
#include "graphics/niche/Drivers/EInk/E0213A367.h"
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h" #include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
#include "graphics/niche/Inputs/TwoButton.h" #include "graphics/niche/Inputs/TwoButton.h"
// Button feedback #include "buzz.h" // Button feedback
#include "buzz.h" #include "einkDetect.h" // Detect display model at runtime
void setupNicheGraphics() void setupNicheGraphics()
{ {
using namespace NicheGraphics; using namespace NicheGraphics;
// Detect E-Ink Model
// -------------------
EInkDetectionResult displayModel = detectEInk();
// SPI // SPI
// ----------------------------- // -----------------------------
@@ -38,7 +44,13 @@ void setupNicheGraphics()
// E-Ink Driver // E-Ink Driver
// ----------------------------- // -----------------------------
Drivers::EInk *driver = new Drivers::LCMEN213EFC1; Drivers::EInk *driver;
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked)
driver = new Drivers::LCMEN213EFC1;
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1
driver = new Drivers::E0213A367;
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES); driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
// InkHUD // InkHUD
@@ -51,7 +63,11 @@ void setupNicheGraphics()
// Set how many FAST updates per FULL update // Set how many FAST updates per FULL update
// Set how unhealthy additional FAST updates beyond this number are // Set how unhealthy additional FAST updates beyond this number are
inkhud->setDisplayResilience(10, 1.5);
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked)
inkhud->setDisplayResilience(10, 1.5);
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1
inkhud->setDisplayResilience(15, 3);
// Select fonts // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
@@ -7,7 +7,8 @@ build_flags =
-Ivariants/heltec_vision_master_e213 -Ivariants/heltec_vision_master_e213
-DHELTEC_VISION_MASTER_E213 -DHELTEC_VISION_MASTER_E213
-DUSE_EINK -DUSE_EINK
-DEINK_DISPLAY_MODEL=GxEPD2_213_FC1 -DGXEPD2_DRIVER_0=GxEPD2_213_FC1
-DGXEPD2_DRIVER_1=GxEPD2_213_E0213A367
-DEINK_WIDTH=250 -DEINK_WIDTH=250
-DEINK_HEIGHT=122 -DEINK_HEIGHT=122
-DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
@@ -16,7 +17,7 @@ build_flags =
-DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip
lewisxhe/PCF8563_Library@^1.0.1 lewisxhe/PCF8563_Library@^1.0.1
upload_speed = 115200 upload_speed = 115200
@@ -0,0 +1,35 @@
#pragma once
#include "configuration.h"
enum class EInkDetectionResult : uint8_t {
LCMEN213EFC1 = 0, // V1.1
E0213A367 = 1, // V1.1.1, V1.2
};
EInkDetectionResult detectEInk()
{
// Test 1: Logic of BUSY pin
// Determines controller IC manufacturer
// Fitipower: busy when LOW
// Solomon Systech: busy when HIGH
// Force display BUSY by holding reset pin active
pinMode(PIN_EINK_RES, OUTPUT);
digitalWrite(PIN_EINK_RES, LOW);
delay(10);
// Read whether pin is HIGH or LOW while busy
pinMode(PIN_EINK_BUSY, INPUT);
bool busyLogic = digitalRead(PIN_EINK_BUSY);
// Test complete. Release pin
pinMode(PIN_EINK_RES, INPUT);
if (busyLogic == LOW)
return EInkDetectionResult::LCMEN213EFC1;
else // busy HIGH
return EInkDetectionResult::E0213A367;
}
+20 -2
View File
@@ -18,13 +18,21 @@
// Shared NicheGraphics components // Shared NicheGraphics components
// -------------------------------- // --------------------------------
#include "graphics/niche/Drivers/EInk/E0213A367.h"
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h" #include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
#include "graphics/niche/Inputs/TwoButton.h" #include "graphics/niche/Inputs/TwoButton.h"
#include "einkDetect.h" // Detect display model at runtime
void setupNicheGraphics() void setupNicheGraphics()
{ {
using namespace NicheGraphics; using namespace NicheGraphics;
// Detect E-Ink Model
// -------------------
EInkDetectionResult displayModel = detectEInk();
// SPI // SPI
// ----------------------------- // -----------------------------
@@ -35,7 +43,13 @@ void setupNicheGraphics()
// E-Ink Driver // E-Ink Driver
// ----------------------------- // -----------------------------
Drivers::EInk *driver = new Drivers::LCMEN213EFC1; Drivers::EInk *driver;
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1.1
driver = new Drivers::LCMEN213EFC1;
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1.1, V1.2
driver = new Drivers::E0213A367;
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES); driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
// InkHUD // InkHUD
@@ -48,7 +62,11 @@ void setupNicheGraphics()
// Set how many FAST updates per FULL update // Set how many FAST updates per FULL update
// Set how unhealthy additional FAST updates beyond this number are // Set how unhealthy additional FAST updates beyond this number are
inkhud->setDisplayResilience(10, 1.5);
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1.1 (unmarked)
inkhud->setDisplayResilience(10, 1.5);
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1.1, V1.2
inkhud->setDisplayResilience(15, 3);
// Select fonts // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
@@ -7,7 +7,8 @@ build_flags =
${esp32s3_base.build_flags} ${esp32s3_base.build_flags}
-I variants/heltec_wireless_paper -I variants/heltec_wireless_paper
-D HELTEC_WIRELESS_PAPER -D HELTEC_WIRELESS_PAPER
-D EINK_DISPLAY_MODEL=GxEPD2_213_FC1 -D GXEPD2_DRIVER_0=GxEPD2_213_FC1
-D GXEPD2_DRIVER_1=GxEPD2_213_E0213A367
-D EINK_WIDTH=250 -D EINK_WIDTH=250
-D EINK_HEIGHT=122 -D EINK_HEIGHT=122
-D USE_EINK -D USE_EINK
@@ -17,7 +18,7 @@ build_flags =
-D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip
lewisxhe/PCF8563_Library@^1.0.1 lewisxhe/PCF8563_Library@^1.0.1
upload_speed = 115200 upload_speed = 115200
+1
View File
@@ -68,6 +68,7 @@
#define TB_LEFT 1 #define TB_LEFT 1
#define TB_RIGHT 2 #define TB_RIGHT 2
#define TB_PRESS 0 // BUTTON_PIN #define TB_PRESS 0 // BUTTON_PIN
#define TB_DIRECTION FALLING
// microphone // microphone
#define ES7210_SCK 47 #define ES7210_SCK 47