mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-14 22:07:25 +00:00
Compare commits
29 Commits
v2.7.1.f35
...
v2.7.2.f6d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f5698ccc0 | ||
|
|
74c735d5fb | ||
|
|
107dec22bd | ||
|
|
0795b21c2b | ||
|
|
a7e516d6f6 | ||
|
|
f6d378255c | ||
|
|
19d831d20d | ||
|
|
00495140bd | ||
|
|
354f149338 | ||
|
|
999e1207a5 | ||
|
|
916587c2a6 | ||
|
|
db4e4e6e53 | ||
|
|
9c08220d24 | ||
|
|
88b299dd41 | ||
|
|
19af2d9e3b | ||
|
|
fa23be4424 | ||
|
|
e1f40c2db9 | ||
|
|
415dc4aa47 | ||
|
|
f2fb473ecf | ||
|
|
f95c77b8bd | ||
|
|
09d4ee1ea7 | ||
|
|
40c586ca97 | ||
|
|
708978911b | ||
|
|
2a26209889 | ||
|
|
1378657206 | ||
|
|
98d010761e | ||
|
|
798b1f4d86 | ||
|
|
29893e0c28 | ||
|
|
1994bb3cd1 |
35
.github/workflows/build_esp32.yml
vendored
35
.github/workflows/build_esp32.yml
vendored
@@ -11,27 +11,30 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-esp32:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- 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
|
||||
id: build
|
||||
uses: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
board: ${{ inputs.board }}
|
||||
remove-debug-flags: >-
|
||||
./arch/esp32/esp32.ini
|
||||
./arch/esp32/esp32s2.ini
|
||||
./arch/esp32/esp32s3.ini
|
||||
./arch/esp32/esp32c3.ini
|
||||
./arch/esp32/esp32c6.ini
|
||||
build-script-path: bin/build-esp32.sh
|
||||
ota-firmware-source: firmware.bin
|
||||
ota-firmware-target: release/bleota.bin
|
||||
artifact-paths: |
|
||||
pio_platform: esp32
|
||||
pio_env: ${{ inputs.board }}
|
||||
pio_target: build
|
||||
ota_firmware_source: firmware.bin
|
||||
ota_firmware_target: release/bleota.bin
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-esp32-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
path: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
#include-web-ui: true
|
||||
arch: esp32
|
||||
|
||||
35
.github/workflows/build_esp32_c3.yml
vendored
35
.github/workflows/build_esp32_c3.yml
vendored
@@ -11,27 +11,30 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-esp32-c3:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- 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
|
||||
id: build
|
||||
uses: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
board: ${{ inputs.board }}
|
||||
remove-debug-flags: >-
|
||||
./arch/esp32/esp32.ini
|
||||
./arch/esp32/esp32s2.ini
|
||||
./arch/esp32/esp32s3.ini
|
||||
./arch/esp32/esp32c3.ini
|
||||
./arch/esp32/esp32c6.ini
|
||||
build-script-path: bin/build-esp32.sh
|
||||
ota-firmware-source: firmware-c3.bin
|
||||
ota-firmware-target: release/bleota-c3.bin
|
||||
artifact-paths: |
|
||||
pio_platform: esp32
|
||||
pio_env: ${{ inputs.board }}
|
||||
pio_target: build
|
||||
ota_firmware_source: firmware-c3.bin
|
||||
ota_firmware_target: release/bleota-c3.bin
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-esp32c3-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
path: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
#include-web-ui: true
|
||||
arch: esp32c3
|
||||
|
||||
35
.github/workflows/build_esp32_c6.yml
vendored
35
.github/workflows/build_esp32_c6.yml
vendored
@@ -11,27 +11,30 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-esp32-c6:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- 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
|
||||
id: build
|
||||
uses: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
board: ${{ inputs.board }}
|
||||
remove-debug-flags: >-
|
||||
./arch/esp32/esp32.ini
|
||||
./arch/esp32/esp32s2.ini
|
||||
./arch/esp32/esp32s3.ini
|
||||
./arch/esp32/esp32c3.ini
|
||||
./arch/esp32/esp32c6.ini
|
||||
build-script-path: bin/build-esp32.sh
|
||||
ota-firmware-source: firmware-c3.bin
|
||||
ota-firmware-target: release/bleota-c3.bin
|
||||
artifact-paths: |
|
||||
pio_platform: esp32
|
||||
pio_env: ${{ inputs.board }}
|
||||
pio_target: build
|
||||
ota_firmware_source: firmware-c3.bin
|
||||
ota_firmware_target: release/bleota-c3.bin
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-esp32c6-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
path: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
#include-web-ui: true
|
||||
arch: esp32c6
|
||||
|
||||
35
.github/workflows/build_esp32_s3.yml
vendored
35
.github/workflows/build_esp32_s3.yml
vendored
@@ -11,27 +11,30 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-esp32-s3:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- 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
|
||||
id: build
|
||||
uses: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
board: ${{ inputs.board }}
|
||||
remove-debug-flags: >-
|
||||
./arch/esp32/esp32.ini
|
||||
./arch/esp32/esp32s2.ini
|
||||
./arch/esp32/esp32s3.ini
|
||||
./arch/esp32/esp32c3.ini
|
||||
./arch/esp32/esp32c6.ini
|
||||
build-script-path: bin/build-esp32.sh
|
||||
ota-firmware-source: firmware-s3.bin
|
||||
ota-firmware-target: release/bleota-s3.bin
|
||||
artifact-paths: |
|
||||
pio_platform: esp32
|
||||
pio_env: ${{ inputs.board }}
|
||||
pio_target: build
|
||||
ota_firmware_source: firmware-s3.bin
|
||||
ota_firmware_target: release/bleota-s3.bin
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-esp32s3-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
path: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
#include-web-ui: true
|
||||
arch: esp32s3
|
||||
|
||||
28
.github/workflows/build_nrf52.yml
vendored
28
.github/workflows/build_nrf52.yml
vendored
@@ -11,20 +11,30 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-nrf52:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- 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
|
||||
id: build
|
||||
uses: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
board: ${{ inputs.board }}
|
||||
build-script-path: bin/build-nrf52.sh
|
||||
artifact-paths: |
|
||||
release/*.hex
|
||||
pio_platform: nrf52
|
||||
pio_env: ${{ inputs.board }}
|
||||
pio_target: build
|
||||
|
||||
- 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/*.elf
|
||||
release/*.zip
|
||||
arch: nrf52840
|
||||
release/*.hex
|
||||
release/*-ota.zip
|
||||
|
||||
24
.github/workflows/build_rpi2040.yml
vendored
24
.github/workflows/build_rpi2040.yml
vendored
@@ -11,18 +11,28 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-rpi2040:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- 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
|
||||
id: build
|
||||
uses: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
board: ${{ inputs.board }}
|
||||
build-script-path: bin/build-rpi2040.sh
|
||||
artifact-paths: |
|
||||
pio_platform: rp2xx0
|
||||
pio_env: ${{ inputs.board }}
|
||||
pio_target: build
|
||||
|
||||
- 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/*.elf
|
||||
arch: rp2040
|
||||
|
||||
24
.github/workflows/build_stm32.yml
vendored
24
.github/workflows/build_stm32.yml
vendored
@@ -11,19 +11,29 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-stm32:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- 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
|
||||
id: build
|
||||
uses: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
board: ${{ inputs.board }}
|
||||
build-script-path: bin/build-stm32.sh
|
||||
artifact-paths: |
|
||||
pio_platform: stm32wl
|
||||
pio_env: ${{ inputs.board }}
|
||||
pio_target: build
|
||||
|
||||
- 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/*.bin
|
||||
release/*.elf
|
||||
arch: stm32
|
||||
|
||||
3
.github/workflows/main_matrix.yml
vendored
3
.github/workflows/main_matrix.yml
vendored
@@ -135,6 +135,7 @@ jobs:
|
||||
board: ${{ matrix.board }}
|
||||
|
||||
build-debian-src:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
uses: ./.github/workflows/build_debian_src.yml
|
||||
with:
|
||||
series: UNRELEASED
|
||||
@@ -425,7 +426,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
publish-firmware:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
needs: [release-firmware]
|
||||
env:
|
||||
|
||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@@ -8,6 +8,7 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
trunk_check:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
name: Trunk Check and Upload
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
@@ -21,6 +22,7 @@ jobs:
|
||||
trunk-token: ${{ secrets.TRUNK_TOKEN }}
|
||||
|
||||
trunk_upgrade:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
# See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#automatic-upgrades
|
||||
name: Trunk Upgrade (PR)
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
1
.github/workflows/sec_sast_semgrep_cron.yml
vendored
1
.github/workflows/sec_sast_semgrep_cron.yml
vendored
@@ -13,6 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
semgrep-full:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: semgrep/semgrep
|
||||
|
||||
1
.github/workflows/stale_bot.yml
vendored
1
.github/workflows/stale_bot.yml
vendored
@@ -11,6 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
stale_issues:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
name: Close Stale Issues
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -12,9 +12,11 @@ permissions:
|
||||
|
||||
jobs:
|
||||
native-tests:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
|
||||
hardware-tests:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
runs-on: test-runner
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
||||
@@ -9,14 +9,14 @@ plugins:
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.447
|
||||
- renovate@41.17.2
|
||||
- renovate@41.23.4
|
||||
- prettier@3.6.2
|
||||
- trufflehog@3.89.2
|
||||
- yamllint@1.37.1
|
||||
- bandit@1.8.5
|
||||
- trivy@0.64.0
|
||||
- bandit@1.8.6
|
||||
- trivy@0.64.1
|
||||
- taplo@0.9.3
|
||||
- ruff@0.12.1
|
||||
- ruff@0.12.2
|
||||
- isort@6.0.1
|
||||
- markdownlint@0.45.0
|
||||
- oxipng@9.1.5
|
||||
|
||||
@@ -7,12 +7,7 @@ MCU=""
|
||||
|
||||
# Variant groups
|
||||
BIGDB_8MB=(
|
||||
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
|
||||
if [[ $FILENAME == *"-tft-"* ]]; then
|
||||
TFT_BUILD=true
|
||||
fi
|
||||
|
||||
# Extract BASENAME from %FILENAME% for later use.r-s3"
|
||||
"picomputer-s3"
|
||||
"unphone"
|
||||
"seeed-sensecap-indicator"
|
||||
"crowpanel-esp32s3"
|
||||
|
||||
@@ -87,6 +87,9 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.7.2" date="2025-07-04">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.2</url>
|
||||
</release>
|
||||
<release version="2.7.1" date="2025-06-27">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.1</url>
|
||||
</release>
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"]
|
||||
["0x239A", "0x002A"],
|
||||
["0x2886", "0x1667"]
|
||||
],
|
||||
"usb_product": "HT-n5262",
|
||||
"mcu": "nrf52840",
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
["0x239A", "0x8029"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x239A", "0x802A"]
|
||||
["0x239A", "0x802A"],
|
||||
["0x2886", "0x0057"]
|
||||
],
|
||||
"usb_product": "T1000-E-BOOT",
|
||||
"mcu": "nrf52840",
|
||||
|
||||
7
debian/changelog
vendored
7
debian/changelog
vendored
@@ -1,4 +1,4 @@
|
||||
meshtasticd (2.7.1.0) UNRELEASED; urgency=medium
|
||||
meshtasticd (2.7.2.0) UNRELEASED; urgency=medium
|
||||
|
||||
[ Austin Lane ]
|
||||
* Initial packaging
|
||||
@@ -25,4 +25,7 @@ meshtasticd (2.7.1.0) UNRELEASED; urgency=medium
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
-- <github-actions[bot]@users.noreply.github.com> Fri, 27 Jun 2025 20:12:21 +0000
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
-- <github-actions[bot]@users.noreply.github.com> Fri, 04 Jul 2025 11:58:01 +0000
|
||||
|
||||
@@ -109,7 +109,7 @@ lib_deps =
|
||||
[device-ui_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||
https://github.com/meshtastic/device-ui/archive/4b7bf369adfa5a7bd419fa8293d21206576d52d0.zip
|
||||
https://github.com/meshtastic/device-ui/archive/8c7092c73425adfda1aac8c6960df06cd85f6d92.zip
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
[environmental_base]
|
||||
@@ -129,7 +129,7 @@ lib_deps =
|
||||
# renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library
|
||||
adafruit/Adafruit MCP9808 Library@2.0.2
|
||||
# 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
|
||||
adafruit/Adafruit INA219@1.2.3
|
||||
# renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor
|
||||
|
||||
@@ -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) {
|
||||
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());
|
||||
perhapsSetRTC(RTCQualityGPS, t);
|
||||
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) {
|
||||
// Clear the GPS buffer if we got an invalid time
|
||||
clearBuffer();
|
||||
}
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
|
||||
@@ -105,7 +105,7 @@ void readFromRTC()
|
||||
*
|
||||
* 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;
|
||||
uint32_t now = millis();
|
||||
@@ -113,7 +113,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
|
||||
#ifdef BUILD_EPOCH
|
||||
if (tv->tv_sec < BUILD_EPOCH) {
|
||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||
return false;
|
||||
return RTCSetResultInvalidTime;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -184,9 +184,9 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
|
||||
readFromRTC();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
return RTCSetResultSuccess;
|
||||
} 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.
|
||||
* @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
|
||||
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);
|
||||
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);
|
||||
return false;
|
||||
return RTCSetResultInvalidTime;
|
||||
} else {
|
||||
return perhapsSetRTC(q, &tv);
|
||||
}
|
||||
|
||||
@@ -22,13 +22,22 @@ enum RTCQuality {
|
||||
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();
|
||||
|
||||
extern uint32_t lastSetFromPhoneNtpOrGps;
|
||||
|
||||
/// 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);
|
||||
bool perhapsSetRTC(RTCQuality q, struct tm &t);
|
||||
RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
|
||||
RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t);
|
||||
|
||||
/// Return a string name for the quality
|
||||
const char *RtcName(RTCQuality quality);
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include "main.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
|
||||
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) || \
|
||||
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
|
||||
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
|
||||
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || \
|
||||
defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
|
||||
{
|
||||
// Start HSPI
|
||||
hspi = new SPIClass(HSPI);
|
||||
@@ -232,6 +235,23 @@ bool EInkDisplay::connect()
|
||||
adafruitDisplay->init();
|
||||
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
|
||||
|
||||
return true;
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#include "GxEPD2_BW.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.
|
||||
*
|
||||
@@ -63,8 +67,15 @@ class EInkDisplay : public OLEDDisplay
|
||||
// Connect to the display
|
||||
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;
|
||||
#endif
|
||||
|
||||
// If display uses HSPI
|
||||
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
|
||||
|
||||
135
src/graphics/GxEPD2Multi.h
Normal file
135
src/graphics/GxEPD2Multi.h
Normal 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;
|
||||
};
|
||||
@@ -171,7 +171,7 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
|
||||
}
|
||||
|
||||
// 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
|
||||
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,
|
||||
std::function<void(uint32_t)> bannerCallback)
|
||||
{
|
||||
LOG_WARN("Show Number Picker");
|
||||
#ifdef USE_EINK
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
|
||||
#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);
|
||||
int32_t rawRGB = uiconfig.screen_rgb_color;
|
||||
if (rawRGB > 0 && rawRGB <= 255255255) {
|
||||
uint8_t r = (rawRGB >> 16) & 0xFF;
|
||||
uint8_t g = (rawRGB >> 8) & 0xFF;
|
||||
uint8_t b = rawRGB & 0xFF;
|
||||
LOG_INFO("Values of r,g,b: %d, %d, %d", r, g, b);
|
||||
uint8_t TFT_MESH_r = (rawRGB >> 16) & 0xFF;
|
||||
uint8_t TFT_MESH_g = (rawRGB >> 8) & 0xFF;
|
||||
uint8_t TFT_MESH_b = rawRGB & 0xFF;
|
||||
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) {
|
||||
TFT_MESH = COLOR565(r, g, b);
|
||||
if (TFT_MESH_r <= 255 && TFT_MESH_g <= 255 && TFT_MESH_b <= 255) {
|
||||
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);
|
||||
#else
|
||||
dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
|
||||
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
|
||||
#endif
|
||||
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
|
||||
#elif defined(USE_SSD1306)
|
||||
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
|
||||
(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);
|
||||
#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 (!dismissedFrames.wifi && isWifiAvailable()) {
|
||||
fsi.positions.wifi = numframes;
|
||||
@@ -969,7 +952,7 @@ void Screen::setFrames(FrameFocus focus)
|
||||
#endif
|
||||
|
||||
// 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
|
||||
// modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
this->frameCount = numframes; // ✅ Save frame count for use in custom overlay
|
||||
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};
|
||||
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
||||
|
||||
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
||||
// just changed)
|
||||
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
|
||||
|
||||
// Focus on a specific frame, in the frame set we just created
|
||||
switch (focus) {
|
||||
@@ -1319,7 +1329,7 @@ int Screen::handleInputEvent(const InputEvent *event)
|
||||
setFastFramerate(); // Draw ASAP
|
||||
#endif
|
||||
if (NotificationRenderer::isOverlayBannerShowing()) {
|
||||
NotificationRenderer::inEvent = event->inputEvent;
|
||||
NotificationRenderer::inEvent = *event;
|
||||
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
|
||||
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
||||
setFastFramerate(); // Draw ASAP
|
||||
|
||||
@@ -92,6 +92,7 @@ class Screen
|
||||
#include "commands.h"
|
||||
#include "concurrency/LockGuard.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "graphics/draw/MenuHandler.h"
|
||||
#include "input/InputBroker.h"
|
||||
#include "mesh/MeshModule.h"
|
||||
#include "modules/AdminModule.h"
|
||||
@@ -308,9 +309,15 @@ class Screen : public concurrency::OSThread
|
||||
void showSimpleBanner(const char *message, uint32_t durationMs = 0);
|
||||
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 requestMenu(graphics::menuHandler::screenMenus menuToShow)
|
||||
{
|
||||
graphics::menuHandler::menuQueue = menuToShow;
|
||||
runNow();
|
||||
}
|
||||
|
||||
void startFirmwareUpdateScreen()
|
||||
{
|
||||
ScreenCmd cmd;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "main.h"
|
||||
#include "modules/AdminModule.h"
|
||||
#include "modules/CannedMessageModule.h"
|
||||
#include "modules/KeyVerificationModule.h"
|
||||
|
||||
extern uint16_t TFT_MESH;
|
||||
|
||||
@@ -136,6 +137,7 @@ void menuHandler::ClockFacePicker()
|
||||
screen->setFrames(Screen::FOCUS_CLOCK);
|
||||
}
|
||||
};
|
||||
bannerOptions.InitialSelected = uiconfig.is_clockface_analog ? 2 : 1;
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
@@ -236,27 +238,25 @@ void menuHandler::clockMenu()
|
||||
|
||||
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) {
|
||||
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext"};
|
||||
optionsArrayPtr = optionsArray;
|
||||
options = 4;
|
||||
} else {
|
||||
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset"};
|
||||
optionsArrayPtr = optionsArray;
|
||||
options = 3;
|
||||
optionsArray[options] = "Reply via Freetext";
|
||||
optionsEnumArray[options++] = Freetext;
|
||||
}
|
||||
|
||||
#ifdef HAS_I2S
|
||||
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext", "Read Aloud"};
|
||||
optionsArrayPtr = optionsArray;
|
||||
options = 5;
|
||||
optionsArray[options] = "Read Aloud";
|
||||
optionsEnumArray[options++] = Aloud;
|
||||
#endif
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Message Action";
|
||||
bannerOptions.optionsArrayPtr = optionsArrayPtr;
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
bannerOptions.optionsCount = options;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == Dismiss) {
|
||||
@@ -275,7 +275,7 @@ void menuHandler::messageResponseMenu()
|
||||
}
|
||||
}
|
||||
#ifdef HAS_I2S
|
||||
else if (selected == 4) {
|
||||
else if (selected == Aloud) {
|
||||
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
|
||||
const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes);
|
||||
|
||||
@@ -288,10 +288,10 @@ void menuHandler::messageResponseMenu()
|
||||
|
||||
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 int optionsEnumArray[6] = {Back};
|
||||
static const char *optionsArray[enumEnd] = {"Back"};
|
||||
static int optionsEnumArray[enumEnd] = {Back};
|
||||
int options = 1;
|
||||
|
||||
#ifdef PIN_EINK_EN
|
||||
@@ -337,8 +337,8 @@ void menuHandler::homeBaseMenu()
|
||||
} else if (selected == Freetext) {
|
||||
cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST);
|
||||
} else if (selected == Bluetooth) {
|
||||
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
menuQueue = bluetooth_toggle_menu;
|
||||
screen->runNow();
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
@@ -353,11 +353,14 @@ void menuHandler::systemBaseMenu()
|
||||
hasSupportBrightness = true;
|
||||
#endif
|
||||
|
||||
enum optionsNumbers { Back, Beeps, Brightness, Reboot, Color, MUI, Test };
|
||||
static const char *optionsArray[7] = {"Back"};
|
||||
static int optionsEnumArray[7] = {Back};
|
||||
enum optionsNumbers { Back, Beeps, Brightness, Reboot, Color, MUI, Test, enumEnd };
|
||||
static const char *optionsArray[enumEnd] = {"Back"};
|
||||
static int optionsEnumArray[enumEnd] = {Back};
|
||||
int options = 1;
|
||||
|
||||
optionsArray[options] = "Reboot";
|
||||
optionsEnumArray[options++] = Reboot;
|
||||
|
||||
optionsArray[options] = "Beeps Action";
|
||||
optionsEnumArray[options++] = Beeps;
|
||||
|
||||
@@ -366,9 +369,6 @@ void menuHandler::systemBaseMenu()
|
||||
optionsEnumArray[options++] = Brightness;
|
||||
}
|
||||
|
||||
optionsArray[options] = "Reboot";
|
||||
optionsEnumArray[options++] = Reboot;
|
||||
|
||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
|
||||
optionsArray[options] = "Screen Color";
|
||||
optionsEnumArray[options++] = Color;
|
||||
@@ -418,21 +418,22 @@ void menuHandler::systemBaseMenu()
|
||||
|
||||
void menuHandler::favoriteBaseMenu()
|
||||
{
|
||||
int options;
|
||||
static const char **optionsArrayPtr;
|
||||
enum optionsNumbers { Back, Preset, Freetext, Remove, enumEnd };
|
||||
static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
|
||||
static int optionsEnumArray[enumEnd] = {Back, Preset};
|
||||
int options = 2;
|
||||
|
||||
if (kb_found) {
|
||||
static const char *optionsArray[] = {"Back", "New Preset Msg", "New Freetext Msg", "Remove Favorite"};
|
||||
optionsArrayPtr = optionsArray;
|
||||
options = 4;
|
||||
} else {
|
||||
static const char *optionsArray[] = {"Back", "New Preset Msg", "Remove Favorite"};
|
||||
optionsArrayPtr = optionsArray;
|
||||
options = 3;
|
||||
optionsArray[options] = "New Freetext Msg";
|
||||
optionsEnumArray[options++] = Freetext;
|
||||
}
|
||||
optionsArray[options] = "Remove Favorite";
|
||||
optionsEnumArray[options++] = Remove;
|
||||
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Favorites Action";
|
||||
bannerOptions.optionsArrayPtr = optionsArrayPtr;
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
bannerOptions.optionsCount = options;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 1) {
|
||||
@@ -449,34 +450,29 @@ void menuHandler::favoriteBaseMenu()
|
||||
|
||||
void menuHandler::positionBaseMenu()
|
||||
{
|
||||
int options;
|
||||
static const char **optionsArrayPtr;
|
||||
static const char *optionsArray[] = {"Back", "GPS Toggle", "Compass"};
|
||||
static const char *optionsArrayCalibrate[] = {"Back", "GPS Toggle", "Compass", "Compass Calibrate"};
|
||||
enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd };
|
||||
|
||||
static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"};
|
||||
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
|
||||
int options = 3;
|
||||
|
||||
if (accelerometerThread) {
|
||||
optionsArrayPtr = optionsArrayCalibrate;
|
||||
options = 4;
|
||||
} else {
|
||||
optionsArrayPtr = optionsArray;
|
||||
options = 3;
|
||||
optionsArray[options] = "Compass Calibrate";
|
||||
optionsEnumArray[options++] = CompassCalibrate;
|
||||
}
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Position Action";
|
||||
bannerOptions.optionsArrayPtr = optionsArrayPtr;
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
bannerOptions.optionsCount = options;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 1) {
|
||||
#if MESHTASTIC_EXCLUDE_GPS
|
||||
menuQueue = menu_none;
|
||||
#else
|
||||
if (selected == GPSToggle) {
|
||||
menuQueue = gps_toggle_menu;
|
||||
screen->runNow();
|
||||
#endif
|
||||
} else if (selected == 2) {
|
||||
} else if (selected == CompassMenu) {
|
||||
menuQueue = compass_point_north_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == 3) {
|
||||
} else if (selected == CompassCalibrate) {
|
||||
accelerometerThread->calibrate(30);
|
||||
}
|
||||
};
|
||||
@@ -485,16 +481,20 @@ void menuHandler::positionBaseMenu()
|
||||
|
||||
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;
|
||||
bannerOptions.message = "Node Action";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 3;
|
||||
bannerOptions.optionsCount = 4;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 1) {
|
||||
if (selected == Favorite) {
|
||||
menuQueue = add_favorite;
|
||||
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;
|
||||
screen->runNow();
|
||||
}
|
||||
@@ -522,6 +522,7 @@ void menuHandler::resetNodeDBMenu()
|
||||
|
||||
void menuHandler::compassNorthMenu()
|
||||
{
|
||||
enum optionsNumbers { Back, Dynamic, Fixed, Freeze };
|
||||
static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "North Directions?";
|
||||
@@ -529,28 +530,28 @@ void menuHandler::compassNorthMenu()
|
||||
bannerOptions.optionsCount = 4;
|
||||
bannerOptions.InitialSelected = uiconfig.compass_mode + 1;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 1) {
|
||||
if (selected == Dynamic) {
|
||||
if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) {
|
||||
uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC;
|
||||
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
|
||||
&uiconfig);
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||
}
|
||||
} else if (selected == 2) {
|
||||
} else if (selected == Fixed) {
|
||||
if (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,
|
||||
&uiconfig);
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||
}
|
||||
} else if (selected == 3) {
|
||||
} else if (selected == Freeze) {
|
||||
if (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,
|
||||
&uiconfig);
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||
}
|
||||
} else if (selected == 0) {
|
||||
} else if (selected == Back) {
|
||||
menuQueue = position_base_menu;
|
||||
screen->runNow();
|
||||
}
|
||||
@@ -561,6 +562,7 @@ void menuHandler::compassNorthMenu()
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
void menuHandler::GPSToggleMenu()
|
||||
{
|
||||
|
||||
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Toggle GPS";
|
||||
@@ -587,6 +589,23 @@ void menuHandler::GPSToggleMenu()
|
||||
}
|
||||
#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()
|
||||
{
|
||||
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"};
|
||||
@@ -677,52 +696,52 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 10;
|
||||
bannerOptions.bannerCallback = [display](int selected) -> void {
|
||||
uint8_t r = 0;
|
||||
uint8_t g = 0;
|
||||
uint8_t b = 0;
|
||||
uint8_t TFT_MESH_r = 0;
|
||||
uint8_t TFT_MESH_g = 0;
|
||||
uint8_t TFT_MESH_b = 0;
|
||||
if (selected == 1) {
|
||||
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
|
||||
} else if (selected == 2) {
|
||||
LOG_INFO("Setting color to Meshtastic Green");
|
||||
r = 103;
|
||||
g = 234;
|
||||
b = 148;
|
||||
TFT_MESH_r = 103;
|
||||
TFT_MESH_g = 234;
|
||||
TFT_MESH_b = 148;
|
||||
} else if (selected == 3) {
|
||||
LOG_INFO("Setting color to Yellow");
|
||||
r = 255;
|
||||
g = 255;
|
||||
b = 128;
|
||||
TFT_MESH_r = 255;
|
||||
TFT_MESH_g = 255;
|
||||
TFT_MESH_b = 128;
|
||||
} else if (selected == 4) {
|
||||
LOG_INFO("Setting color to Red");
|
||||
r = 255;
|
||||
g = 64;
|
||||
b = 64;
|
||||
TFT_MESH_r = 255;
|
||||
TFT_MESH_g = 64;
|
||||
TFT_MESH_b = 64;
|
||||
} else if (selected == 5) {
|
||||
LOG_INFO("Setting color to Orange");
|
||||
r = 255;
|
||||
g = 160;
|
||||
b = 20;
|
||||
TFT_MESH_r = 255;
|
||||
TFT_MESH_g = 160;
|
||||
TFT_MESH_b = 20;
|
||||
} else if (selected == 6) {
|
||||
LOG_INFO("Setting color to Purple");
|
||||
r = 204;
|
||||
g = 153;
|
||||
b = 255;
|
||||
TFT_MESH_r = 204;
|
||||
TFT_MESH_g = 153;
|
||||
TFT_MESH_b = 255;
|
||||
} else if (selected == 7) {
|
||||
LOG_INFO("Setting color to Teal");
|
||||
r = 64;
|
||||
g = 224;
|
||||
b = 208;
|
||||
TFT_MESH_r = 64;
|
||||
TFT_MESH_g = 224;
|
||||
TFT_MESH_b = 208;
|
||||
} else if (selected == 8) {
|
||||
LOG_INFO("Setting color to Pink");
|
||||
r = 255;
|
||||
g = 105;
|
||||
b = 180;
|
||||
TFT_MESH_r = 255;
|
||||
TFT_MESH_g = 105;
|
||||
TFT_MESH_b = 180;
|
||||
} else if (selected == 9) {
|
||||
LOG_INFO("Setting color to White");
|
||||
r = 255;
|
||||
g = 255;
|
||||
b = 255;
|
||||
TFT_MESH_r = 255;
|
||||
TFT_MESH_g = 255;
|
||||
TFT_MESH_b = 255;
|
||||
}
|
||||
|
||||
#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->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
|
||||
TFT_MESH = TFT_MESH_OVERRIDE;
|
||||
#else
|
||||
TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
|
||||
#endif
|
||||
} 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)
|
||||
@@ -746,10 +765,10 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
|
||||
#endif
|
||||
|
||||
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;
|
||||
} 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);
|
||||
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
|
||||
@@ -778,7 +797,7 @@ void menuHandler::rebootMenu()
|
||||
|
||||
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);
|
||||
nodeDB->set_favorite(true, nodenum);
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||
@@ -869,6 +888,37 @@ void menuHandler::wifiToggleMenu()
|
||||
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)
|
||||
{
|
||||
if (menuQueue != menu_none)
|
||||
@@ -935,6 +985,18 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case wifi_toggle_menu:
|
||||
wifiToggleMenu();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
#if HAS_SCREEN
|
||||
#include "configuration.h"
|
||||
namespace graphics
|
||||
{
|
||||
@@ -25,7 +27,11 @@ class menuHandler
|
||||
remove_favorite,
|
||||
test_menu,
|
||||
number_test,
|
||||
wifi_toggle_menu
|
||||
wifi_toggle_menu,
|
||||
key_verification_init,
|
||||
key_verification_final_prompt,
|
||||
bluetooth_toggle_menu,
|
||||
throttle_message
|
||||
};
|
||||
static screenMenus menuQueue;
|
||||
|
||||
@@ -55,6 +61,10 @@ class menuHandler
|
||||
static void numberTest();
|
||||
static void wifiBaseMenu();
|
||||
static void wifiToggleMenu();
|
||||
static void keyVerificationInitMenu();
|
||||
static void keyVerificationFinalPrompt();
|
||||
static void BluetoothToggleMenu();
|
||||
};
|
||||
|
||||
} // namespace graphics
|
||||
} // namespace graphics
|
||||
#endif
|
||||
@@ -26,7 +26,7 @@ extern bool hasUnreadMessage;
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
char NotificationRenderer::inEvent = INPUT_BROKER_NONE;
|
||||
InputEvent NotificationRenderer::inEvent;
|
||||
int8_t NotificationRenderer::curSelected = 0;
|
||||
char NotificationRenderer::alertBannerMessage[256] = {0};
|
||||
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 ret = 1;
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
ret *= 10;
|
||||
}
|
||||
return ret;
|
||||
@@ -72,14 +72,31 @@ void NotificationRenderer::resetBanner()
|
||||
{
|
||||
alertBannerMessage[0] = '\0';
|
||||
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);
|
||||
}
|
||||
|
||||
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||
{
|
||||
if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0')
|
||||
resetBanner();
|
||||
if (!isOverlayBannerShowing() || pauseBanner)
|
||||
return;
|
||||
switch (current_notification_type) {
|
||||
case notificationTypeEnum::none:
|
||||
// Do nothing - no notification to display
|
||||
break;
|
||||
case notificationTypeEnum::text_banner:
|
||||
case notificationTypeEnum::selection_picker:
|
||||
drawAlertBannerOverlay(display, state);
|
||||
@@ -112,31 +129,40 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
|
||||
// modulo to extract
|
||||
uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1));
|
||||
// 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) {
|
||||
currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1));
|
||||
} else {
|
||||
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) {
|
||||
currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1));
|
||||
} else {
|
||||
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++;
|
||||
} else if (inEvent == INPUT_BROKER_LEFT) {
|
||||
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
|
||||
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();
|
||||
return;
|
||||
}
|
||||
if (curSelected == numDigits) {
|
||||
resetBanner();
|
||||
alertBannerCallback(currentNumber);
|
||||
resetBanner();
|
||||
return;
|
||||
}
|
||||
|
||||
inEvent = INPUT_BROKER_NONE;
|
||||
inEvent.inputEvent = INPUT_BROKER_NONE;
|
||||
if (alertBannerMessage[0] == '\0')
|
||||
return;
|
||||
|
||||
@@ -144,12 +170,12 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
|
||||
const char *linePointers[totalLines + 1] = {0}; // this is sort of a dynamic allocation
|
||||
|
||||
// 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];
|
||||
}
|
||||
std::string digits = " ";
|
||||
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
|
||||
digits += std::to_string((currentNumber % (pow_of_10(numDigits - i))) / (pow_of_10(numDigits - i - 1))) + " ";
|
||||
if (curSelected == i) {
|
||||
@@ -190,16 +216,18 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
|
||||
}
|
||||
|
||||
// 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--;
|
||||
} 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++;
|
||||
} else if (inEvent == INPUT_BROKER_SELECT) {
|
||||
resetBanner();
|
||||
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
|
||||
alertBannerCallback(selectedNodenum);
|
||||
|
||||
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
|
||||
resetBanner();
|
||||
return;
|
||||
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
|
||||
alertBannerUntil != 0) {
|
||||
resetBanner();
|
||||
return;
|
||||
}
|
||||
|
||||
if (curSelected == -1)
|
||||
@@ -207,7 +235,7 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
|
||||
if (curSelected == alertBannerOptions)
|
||||
curSelected = 0;
|
||||
|
||||
inEvent = INPUT_BROKER_NONE;
|
||||
inEvent.inputEvent = INPUT_BROKER_NONE;
|
||||
if (alertBannerMessage[0] == '\0')
|
||||
return;
|
||||
|
||||
@@ -305,11 +333,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
||||
|
||||
// Handle input
|
||||
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--;
|
||||
} 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++;
|
||||
} else if (inEvent == INPUT_BROKER_SELECT) {
|
||||
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
|
||||
if (optionsEnumPtr != nullptr) {
|
||||
alertBannerCallback(optionsEnumPtr[curSelected]);
|
||||
optionsEnumPtr = nullptr;
|
||||
@@ -317,8 +345,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
||||
alertBannerCallback(curSelected);
|
||||
}
|
||||
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();
|
||||
return;
|
||||
}
|
||||
|
||||
if (curSelected == -1)
|
||||
@@ -326,12 +357,14 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
||||
if (curSelected == alertBannerOptions)
|
||||
curSelected = 0;
|
||||
} 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();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
inEvent = INPUT_BROKER_NONE;
|
||||
inEvent.inputEvent = INPUT_BROKER_NONE;
|
||||
if (alertBannerMessage[0] == '\0')
|
||||
return;
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace graphics
|
||||
class NotificationRenderer
|
||||
{
|
||||
public:
|
||||
static char inEvent;
|
||||
static InputEvent inEvent;
|
||||
static char inKeypress;
|
||||
static int8_t curSelected;
|
||||
static char alertBannerMessage[256];
|
||||
static uint32_t alertBannerUntil; // 0 is a special case meaning forever
|
||||
|
||||
84
src/graphics/niche/Drivers/EInk/E0213A367.cpp
Normal file
84
src/graphics/niche/Drivers/EInk/E0213A367.cpp
Normal file
@@ -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
|
||||
41
src/graphics/niche/Drivers/EInk/E0213A367.h
Normal file
41
src/graphics/niche/Drivers/EInk/E0213A367.h
Normal file
@@ -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
|
||||
41
src/graphics/niche/Drivers/EInk/SSD1682.cpp
Normal file
41
src/graphics/niche/Drivers/EInk/SSD1682.cpp
Normal file
@@ -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
src/graphics/niche/Drivers/EInk/SSD1682.h
Normal file
31
src/graphics/niche/Drivers/EInk/SSD1682.h
Normal 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
|
||||
@@ -369,6 +369,14 @@ NodeDB::NodeDB()
|
||||
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)))
|
||||
saveWhat |= SEGMENT_DEVICESTATE;
|
||||
if (nodeDatabaseCRC != crc32Buffer(&nodeDatabase, sizeof(nodeDatabase)))
|
||||
|
||||
@@ -246,8 +246,10 @@ void PacketHistory::insert(PacketRecord &r)
|
||||
#if RECENT_WARN_AGE > 0
|
||||
if (tu->rxTimeMsec && (OldtrxTimeMsec < RECENT_WARN_AGE)) {
|
||||
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,
|
||||
RECENT_WARN_AGE / 1000);
|
||||
#endif
|
||||
} else {
|
||||
// debug only
|
||||
#if VERBOSE_PACKET_HISTORY
|
||||
@@ -275,7 +277,9 @@ void PacketHistory::insert(PacketRecord &r)
|
||||
#endif
|
||||
|
||||
if (r.rxTimeMsec == 0) {
|
||||
#if VERBOSE_PACKET_HISTORY
|
||||
LOG_WARN("Packet History - insert: I will not store packet with rxTimeMsec = 0.");
|
||||
#endif
|
||||
return; // Return early if we can't update the history
|
||||
}
|
||||
|
||||
|
||||
@@ -645,10 +645,6 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
if (power > loraMaxPower) // Clamp power to maximum defined level
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,32 @@
|
||||
#include "aes-ccm.h"
|
||||
#if !MESHTASTIC_EXCLUDE_PKI
|
||||
|
||||
/**
|
||||
* Constant-time comparison of two byte arrays
|
||||
*
|
||||
* @param a First byte array to compare
|
||||
* @param b Second byte array to compare
|
||||
* @param len Number of bytes to compare
|
||||
* @return 0 if arrays are equal, -1 if different or if inputs are invalid
|
||||
*/
|
||||
static int constant_time_compare(const void *a_, const void *b_, size_t len)
|
||||
{
|
||||
/* Cast to volatile to prevent the compiler from optimizing out their comparison. */
|
||||
const volatile uint8_t *volatile a = (const volatile uint8_t *volatile)a_;
|
||||
const volatile uint8_t *volatile b = (const volatile uint8_t *volatile)b_;
|
||||
if (len == 0)
|
||||
return 0;
|
||||
if (a == NULL || b == NULL)
|
||||
return -1;
|
||||
size_t i;
|
||||
volatile uint8_t d = 0U;
|
||||
for (i = 0U; i < len; i++) {
|
||||
d |= (a[i] ^ b[i]);
|
||||
}
|
||||
/* Constant time bit arithmetic to convert d > 0 to -1 and d = 0 to 0. */
|
||||
return (1 & ((d - 1) >> 8)) - 1;
|
||||
}
|
||||
|
||||
static void WPA_PUT_BE16(uint8_t *a, uint16_t val)
|
||||
{
|
||||
a[0] = val >> 8;
|
||||
@@ -146,7 +172,7 @@ bool aes_ccm_ad(const uint8_t *key, size_t key_len, const uint8_t *nonce, size_t
|
||||
aes_ccm_encr(L, crypt, crypt_len, plain, a);
|
||||
aes_ccm_auth_start(M, L, nonce, aad, aad_len, crypt_len, x);
|
||||
aes_ccm_auth(plain, crypt_len, x);
|
||||
if (memcmp(x, t, M) != 0) { // FIXME make const comp
|
||||
if (constant_time_compare(x, t, M) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -13,8 +13,9 @@ template <class T> constexpr const T &clamp(const T &v, const T &lo, const T &hi
|
||||
|
||||
#if HAS_SCREEN
|
||||
#define IF_SCREEN(X) \
|
||||
if (screen) \
|
||||
X;
|
||||
if (screen) { \
|
||||
X; \
|
||||
}
|
||||
#else
|
||||
#define IF_SCREEN(...)
|
||||
#endif
|
||||
|
||||
@@ -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,
|
||||
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
|
||||
InputEvent event = {.inputEvent = (input_broker_event)inputEvent.event_code,
|
||||
.kbchar = (unsigned char)inputEvent.kb_char,
|
||||
|
||||
@@ -716,7 +716,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
|
||||
}
|
||||
|
||||
// Backspace
|
||||
if (event->inputEvent == INPUT_BROKER_BACK) {
|
||||
if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) {
|
||||
payload = 0x08;
|
||||
lastTouchMillis = millis();
|
||||
runOnce();
|
||||
@@ -739,7 +739,8 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
|
||||
}
|
||||
|
||||
// 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;
|
||||
freetext = "";
|
||||
cursor = 0;
|
||||
@@ -989,6 +990,7 @@ int32_t CannedMessageModule::runOnce()
|
||||
}
|
||||
this->cursor--;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
case INPUT_BROKER_MSG_TAB: // Tab key: handled by input handler
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#include "KeyVerificationModule.h"
|
||||
#include "MeshService.h"
|
||||
#include "RTC.h"
|
||||
#include "graphics/draw/MenuHandler.h"
|
||||
#include "main.h"
|
||||
#include "meshUtils.h"
|
||||
#include "modules/AdminModule.h"
|
||||
#include <SHA256.h>
|
||||
|
||||
@@ -48,18 +50,22 @@ AdminMessageHandleResult KeyVerificationModule::handleAdminMessageForModule(cons
|
||||
bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_KeyVerification *r)
|
||||
{
|
||||
updateState();
|
||||
if (mp.pki_encrypted == false)
|
||||
if (mp.pki_encrypted == 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;
|
||||
}
|
||||
if (currentState == KEY_VERIFICATION_IDLE) {
|
||||
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 &&
|
||||
r->hash1.size == 0) {
|
||||
if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 &&
|
||||
r->hash1.size == 0) {
|
||||
memcpy(hash2, r->hash2.bytes, 32);
|
||||
if (screen)
|
||||
screen->showSimpleBanner("Enter Security Number", 30000);
|
||||
IF_SCREEN(screen->showNumberPicker("Enter Security Number", 60000, 6, [](int number_picked) -> void {
|
||||
keyVerificationModule->processSecurityNumber(number_picked);
|
||||
});)
|
||||
|
||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||
cn->level = meshtastic_LogRecord_Level_WARNING;
|
||||
@@ -79,23 +85,18 @@ bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
|
||||
memset(message, 0, sizeof(message));
|
||||
sprintf(message, "Verification: \n");
|
||||
generateVerificationCode(message + 15);
|
||||
static const char *optionsArray[] = {"ACCEPT", "REJECT"};
|
||||
LOG_INFO("Hash1 matches!");
|
||||
if (screen) {
|
||||
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 == 0) {
|
||||
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
|
||||
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(options);
|
||||
}
|
||||
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(currentRemoteNode);
|
||||
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(options);)
|
||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||
cn->level = meshtastic_LogRecord_Level_WARNING;
|
||||
sprintf(cn->message, "Final confirmation for incoming manual key verification %s", message);
|
||||
@@ -120,6 +121,7 @@ bool KeyVerificationModule::sendInitialRequest(NodeNum remoteNode)
|
||||
// generate nonce
|
||||
updateState();
|
||||
if (currentState != KEY_VERIFICATION_IDLE) {
|
||||
IF_SCREEN(graphics::menuHandler::menuQueue = graphics::menuHandler::throttle_message;)
|
||||
return false;
|
||||
}
|
||||
currentNonce = random();
|
||||
@@ -190,11 +192,8 @@ meshtastic_MeshPacket *KeyVerificationModule::allocReply()
|
||||
responsePacket = allocDataProtobuf(response);
|
||||
|
||||
responsePacket->pki_encrypted = true;
|
||||
if (screen) {
|
||||
snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000);
|
||||
screen->showSimpleBanner(message, 30000);
|
||||
LOG_WARN("%s", message);
|
||||
}
|
||||
IF_SCREEN(snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000);
|
||||
screen->showSimpleBanner(message, 30000); LOG_WARN("%s", message);)
|
||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||
cn->level = meshtastic_LogRecord_Level_WARNING;
|
||||
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;
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
currentState = KEY_VERIFICATION_SENDER_AWAITING_USER;
|
||||
memset(message, 0, sizeof(message));
|
||||
sprintf(message, "Verification: \n");
|
||||
generateVerificationCode(message + 15); // send the toPhone packet
|
||||
if (screen) {
|
||||
screen->showSimpleBanner(message, 30000);
|
||||
}
|
||||
IF_SCREEN(screen->requestMenu(graphics::menuHandler::key_verification_final_prompt);)
|
||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||
cn->level = meshtastic_LogRecord_Level_WARNING;
|
||||
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()
|
||||
{
|
||||
if (currentState != KEY_VERIFICATION_IDLE) {
|
||||
// check for the 30 second timeout
|
||||
// check for the 60 second timeout
|
||||
if (currentNonceTimestamp < getTime() - 60) {
|
||||
resetToIdle();
|
||||
} else {
|
||||
|
||||
@@ -27,6 +27,8 @@ class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification>
|
||||
}*/
|
||||
virtual bool wantUIFrame() { return false; };
|
||||
bool sendInitialRequest(NodeNum remoteNode);
|
||||
void generateVerificationCode(char *); // fills char with the user readable verification code
|
||||
uint32_t getCurrentRemoteNode() { return currentRemoteNode; }
|
||||
|
||||
protected:
|
||||
/* Called to handle a particular incoming message
|
||||
@@ -56,9 +58,8 @@ class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification>
|
||||
char message[40] = {0};
|
||||
|
||||
void processSecurityNumber(uint32_t);
|
||||
void updateState(); // check the timeouts and maybe reset the state to idle
|
||||
void resetToIdle(); // Zero out module state
|
||||
void generateVerificationCode(char *); // fills char with the user readable verification code
|
||||
void updateState(); // check the timeouts and maybe reset the state to idle
|
||||
void resetToIdle(); // Zero out module state
|
||||
};
|
||||
|
||||
extern KeyVerificationModule *keyVerificationModule;
|
||||
@@ -29,6 +29,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
||||
uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
|
||||
size_t numBytes = 0;
|
||||
bool hasChecked = false;
|
||||
bool phoneWants = false;
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce() override
|
||||
@@ -38,10 +39,10 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
||||
for (uint8_t i = 0; i < queue_size; i++) {
|
||||
handleToRadio(nimble_queue.at(i).data(), nimble_queue.at(i).length());
|
||||
}
|
||||
LOG_WARN("Queue_size %u", queue_size);
|
||||
LOG_DEBUG("Queue_size %u", queue_size);
|
||||
queue_size = 0;
|
||||
}
|
||||
if (hasChecked == false) {
|
||||
if (hasChecked == false && phoneWants == true) {
|
||||
numBytes = getFromRadio(fromRadioBytes);
|
||||
hasChecked = true;
|
||||
}
|
||||
@@ -98,9 +99,12 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
|
||||
{
|
||||
virtual void onRead(NimBLECharacteristic *pCharacteristic)
|
||||
{
|
||||
while (!bluetoothPhoneAPI->hasChecked) {
|
||||
int tries = 0;
|
||||
bluetoothPhoneAPI->phoneWants = true;
|
||||
while (!bluetoothPhoneAPI->hasChecked && tries < 100) {
|
||||
bluetoothPhoneAPI->setIntervalFromNow(0);
|
||||
delay(20);
|
||||
tries++;
|
||||
}
|
||||
std::lock_guard<std::mutex> guard(bluetoothPhoneAPI->nimble_mutex);
|
||||
std::string fromRadioByteString(bluetoothPhoneAPI->fromRadioBytes,
|
||||
@@ -111,6 +115,7 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
|
||||
bluetoothPhoneAPI->setIntervalFromNow(0);
|
||||
bluetoothPhoneAPI->numBytes = 0;
|
||||
bluetoothPhoneAPI->hasChecked = false;
|
||||
bluetoothPhoneAPI->phoneWants = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -186,7 +191,12 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
|
||||
new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
|
||||
|
||||
if (bluetoothPhoneAPI) {
|
||||
std::lock_guard<std::mutex> guard(bluetoothPhoneAPI->nimble_mutex);
|
||||
bluetoothPhoneAPI->close();
|
||||
bluetoothPhoneAPI->hasChecked = false;
|
||||
bluetoothPhoneAPI->phoneWants = false;
|
||||
bluetoothPhoneAPI->numBytes = 0;
|
||||
bluetoothPhoneAPI->queue_size = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
35
variants/heltec_vision_master_e213/einkDetect.h
Normal file
35
variants/heltec_vision_master_e213/einkDetect.h
Normal file
@@ -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
|
||||
// --------------------------------
|
||||
#include "graphics/niche/Drivers/EInk/E0213A367.h"
|
||||
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
|
||||
#include "graphics/niche/Inputs/TwoButton.h"
|
||||
|
||||
// Button feedback
|
||||
#include "buzz.h"
|
||||
#include "buzz.h" // Button feedback
|
||||
#include "einkDetect.h" // Detect display model at runtime
|
||||
|
||||
void setupNicheGraphics()
|
||||
{
|
||||
using namespace NicheGraphics;
|
||||
|
||||
// Detect E-Ink Model
|
||||
// -------------------
|
||||
|
||||
EInkDetectionResult displayModel = detectEInk();
|
||||
|
||||
// SPI
|
||||
// -----------------------------
|
||||
|
||||
@@ -38,7 +44,13 @@ void setupNicheGraphics()
|
||||
// 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);
|
||||
|
||||
// InkHUD
|
||||
@@ -51,7 +63,11 @@ void setupNicheGraphics()
|
||||
|
||||
// Set how many FAST updates per FULL update
|
||||
// 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
|
||||
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
|
||||
|
||||
@@ -7,7 +7,8 @@ build_flags =
|
||||
-Ivariants/heltec_vision_master_e213
|
||||
-DHELTEC_VISION_MASTER_E213
|
||||
-DUSE_EINK
|
||||
-DEINK_DISPLAY_MODEL=GxEPD2_213_FC1
|
||||
-DGXEPD2_DRIVER_0=GxEPD2_213_FC1
|
||||
-DGXEPD2_DRIVER_1=GxEPD2_213_E0213A367
|
||||
-DEINK_WIDTH=250
|
||||
-DEINK_HEIGHT=122
|
||||
-DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
|
||||
@@ -16,7 +17,7 @@ build_flags =
|
||||
-DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
|
||||
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
|
||||
upload_speed = 115200
|
||||
|
||||
|
||||
35
variants/heltec_wireless_paper/einkDetect.h
Normal file
35
variants/heltec_wireless_paper/einkDetect.h
Normal file
@@ -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;
|
||||
}
|
||||
@@ -18,13 +18,21 @@
|
||||
|
||||
// Shared NicheGraphics components
|
||||
// --------------------------------
|
||||
#include "graphics/niche/Drivers/EInk/E0213A367.h"
|
||||
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
|
||||
#include "graphics/niche/Inputs/TwoButton.h"
|
||||
|
||||
#include "einkDetect.h" // Detect display model at runtime
|
||||
|
||||
void setupNicheGraphics()
|
||||
{
|
||||
using namespace NicheGraphics;
|
||||
|
||||
// Detect E-Ink Model
|
||||
// -------------------
|
||||
|
||||
EInkDetectionResult displayModel = detectEInk();
|
||||
|
||||
// SPI
|
||||
// -----------------------------
|
||||
|
||||
@@ -35,7 +43,13 @@ void setupNicheGraphics()
|
||||
// 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);
|
||||
|
||||
// InkHUD
|
||||
@@ -48,7 +62,11 @@ void setupNicheGraphics()
|
||||
|
||||
// Set how many FAST updates per FULL update
|
||||
// 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
|
||||
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
|
||||
|
||||
@@ -7,7 +7,8 @@ build_flags =
|
||||
${esp32s3_base.build_flags}
|
||||
-I variants/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_HEIGHT=122
|
||||
-D USE_EINK
|
||||
@@ -17,7 +18,7 @@ build_flags =
|
||||
-D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
|
||||
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
|
||||
upload_speed = 115200
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
#define TB_LEFT 1
|
||||
#define TB_RIGHT 2
|
||||
#define TB_PRESS 0 // BUTTON_PIN
|
||||
#define TB_DIRECTION FALLING
|
||||
|
||||
// microphone
|
||||
#define ES7210_SCK 47
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[VERSION]
|
||||
major = 2
|
||||
minor = 7
|
||||
build = 1
|
||||
build = 2
|
||||
|
||||
Reference in New Issue
Block a user