Compare commits

..

14 Commits

Author SHA1 Message Date
Thomas Göttgens
45ecd139e5 Merge branch 'master' into indicator-comms 2025-07-06 14:38:50 +02:00
Thomas Göttgens
79db7a5208 WIP: GPS works 2025-07-04 00:24:33 +02:00
Thomas Göttgens
7289b2a972 Update src/main.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 00:15:52 +02:00
Thomas Göttgens
9356be3f8f Update src/mesh/comms/FakeI2C.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 00:15:32 +02:00
Thomas Göttgens
cce1b050c8 Update src/mesh/comms/FakeUART.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 00:15:24 +02:00
Thomas Göttgens
fd5ff679f3 WIP 2025-07-02 23:41:51 +02:00
Thomas Göttgens
7a7ef5f0c9 Merge branch 'master' into indicator-comms 2025-07-02 17:21:23 +02:00
Thomas Göttgens
f084a8a11d Merge branch 'master' into indicator-comms 2025-06-05 14:35:04 +02:00
Thomas Göttgens
37857941bf Merge branch 'master' into indicator-comms 2025-05-23 15:53:16 +02:00
Thomas Göttgens
d544b41ab7 Merge branch 'master' into indicator-comms 2025-03-31 11:09:44 +02:00
Thomas Göttgens
8c53ce82f2 don't build FakeUART on other platforms 2025-03-04 11:11:25 +01:00
Thomas Göttgens
7ee95f2a0c Merge branch 'indicator-comms' of github.com:meshtastic/firmware into indicator-comms 2025-03-04 10:39:35 +01:00
Thomas Göttgens
65b50babee get sensor and NMEA data from indicator. Will test tomorrow, don't merge yet. 2025-03-04 01:56:56 +01:00
Thomas Göttgens
3c30821337 get sensor and NMEA data from indicator. Will test tomorrow, don't merge yet. 2025-03-04 01:53:18 +01:00
580 changed files with 3011 additions and 7645 deletions

37
.github/workflows/build_esp32.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Build ESP32
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-esp32:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build ESP32
id: build
uses: ./.github/actions/build-variant
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: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32

37
.github/workflows/build_esp32_c3.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Build ESP32-C3
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-esp32-c3:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build ESP32-C3
id: build
uses: ./.github/actions/build-variant
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: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32c3

37
.github/workflows/build_esp32_c6.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Build ESP32-C6
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-esp32-c6:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build ESP32-C6
id: build
uses: ./.github/actions/build-variant
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: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32c6

37
.github/workflows/build_esp32_s3.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Build ESP32-S3
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-esp32-s3:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build ESP32-S3
id: build
uses: ./.github/actions/build-variant
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: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32s3

View File

@@ -1,66 +0,0 @@
name: Build
on:
workflow_call:
inputs:
version:
required: true
type: string
platform:
required: true
type: string
pio_env:
required: true
type: string
permissions: read-all
jobs:
pio-build:
name: build-${{ inputs.platform }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Set OTA firmware source and target
if: startsWith(inputs.platform, 'esp32')
id: ota_dir
env:
PIO_PLATFORM: ${{ inputs.platform }}
run: |
if [ "$PIO_PLATFORM" = "esp32s3" ]; then
echo "src=firmware-s3.bin" >> $GITHUB_OUTPUT
echo "tgt=release/bleota-s3.bin" >> $GITHUB_OUTPUT
elif [ "$PIO_PLATFORM" = "esp32c3" ] || [ "$PIO_PLATFORM" = "esp32c6" ]; then
echo "src=firmware-c3.bin" >> $GITHUB_OUTPUT
echo "tgt=release/bleota-c3.bin" >> $GITHUB_OUTPUT
elif [ "$PIO_PLATFORM" = "esp32" ]; then
echo "src=firmware.bin" >> $GITHUB_OUTPUT
echo "tgt=release/bleota.bin" >> $GITHUB_OUTPUT
fi
- name: Build ${{ inputs.platform }}
id: build
uses: meshtastic/gh-action-firmware@main
with:
pio_platform: ${{ inputs.platform }}
pio_env: ${{ inputs.pio_env }}
pio_target: build
ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }}
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
overwrite: true
path: |
release/*.bin
release/*.elf
release/*.uf2
release/*.hex
release/*-ota.zip

30
.github/workflows/build_nrf52.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Build NRF52
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-nrf52:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build NRF52
id: build
uses: ./.github/actions/build-variant
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
build-script-path: bin/build-nrf52.sh
artifact-paths: |
release/*.hex
release/*.uf2
release/*.elf
release/*.zip
arch: nrf52840

28
.github/workflows/build_rpi2040.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Build RPI2040
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-rpi2040:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Raspberry Pi 2040
id: build
uses: ./.github/actions/build-variant
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
build-script-path: bin/build-rpi2040.sh
artifact-paths: |
release/*.uf2
release/*.elf
arch: rp2040

29
.github/workflows/build_stm32.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Build STM32
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-stm32:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build STM32WL
id: build
uses: ./.github/actions/build-variant
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
build-script-path: bin/build-stm32.sh
artifact-paths: |
release/*.hex
release/*.bin
release/*.elf
arch: stm32

View File

@@ -30,11 +30,7 @@ jobs:
strategy:
fail-fast: false
matrix:
series:
- jammy # 22.04
- noble # 24.04
- plucky # 25.04
- questing # 25.10
series: [plucky, oracular, noble, jammy]
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: ppa:meshtastic/daily

View File

@@ -30,31 +30,18 @@ jobs:
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- check
runs-on: ubuntu-24.04
arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32, check]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
- id: checkout
uses: actions/checkout@v4
name: Checkout base
- id: jsonStep
run: |
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
else
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick)
fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
@@ -65,25 +52,9 @@ jobs:
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
stm32: ${{ steps.jsonStep.outputs.stm32 }}
check: ${{ steps.jsonStep.outputs.check }}
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
id: version
env:
BUILD_LOCATION: local
outputs:
long: ${{ steps.version.outputs.long }}
deb: ${{ steps.version.outputs.deb }}
check:
needs: setup
strategy:
@@ -101,95 +72,69 @@ jobs:
run: bin/check-all.sh ${{ matrix.board }}
build-esp32:
needs: [setup, version]
needs: setup
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
uses: ./.github/workflows/build_firmware.yml
uses: ./.github/workflows/build_esp32.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32
board: ${{ matrix.board }}
build-esp32s3:
needs: [setup, version]
build-esp32-s3:
needs: setup
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_firmware.yml
uses: ./.github/workflows/build_esp32_s3.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32s3
board: ${{ matrix.board }}
build-esp32c3:
needs: [setup, version]
build-esp32-c3:
needs: setup
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_firmware.yml
uses: ./.github/workflows/build_esp32_c3.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c3
board: ${{ matrix.board }}
build-esp32c6:
needs: [setup, version]
build-esp32-c6:
needs: setup
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
uses: ./.github/workflows/build_firmware.yml
uses: ./.github/workflows/build_esp32_c6.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c6
board: ${{ matrix.board }}
build-nrf52840:
needs: [setup, version]
build-nrf52:
needs: setup
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_firmware.yml
uses: ./.github/workflows/build_nrf52.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: nrf52840
board: ${{ matrix.board }}
build-rp2040:
needs: [setup, version]
build-rpi2040:
needs: setup
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_firmware.yml
uses: ./.github/workflows/build_rpi2040.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2040
build-rp2350:
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2350
board: ${{ matrix.board }}
build-stm32:
needs: [setup, version]
needs: setup
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
uses: ./.github/workflows/build_firmware.yml
uses: ./.github/workflows/build_stm32.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: stm32
board: ${{ matrix.board }}
build-debian-src:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
@@ -264,26 +209,16 @@ jobs:
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32]
runs-on: ubuntu-latest
needs:
[
version,
build-esp32,
build-esp32s3,
build-esp32c3,
build-esp32c6,
build-nrf52840,
build-rp2040,
build-rp2350,
build-esp32-s3,
build-esp32-c3,
build-esp32-c6,
build-nrf52,
build-rpi2040,
build-stm32,
]
steps:
@@ -302,13 +237,17 @@ jobs:
- name: Display structure of downloaded files
run: ls -R
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Move files up
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v4
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}
overwrite: true
path: |
./firmware-*.bin
@@ -324,7 +263,7 @@ jobs:
- uses: actions/download-artifact@v4
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}
merge-multiple: true
path: ./output
@@ -338,12 +277,12 @@ jobs:
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v4
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: ./*.elf
retention-days: 30
@@ -351,8 +290,8 @@ jobs:
- uses: scruplelesswizard/comment-artifact@main
if: ${{ github.event_name == 'pull_request' }}
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}
description: "Download firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}
release-artifacts:
@@ -361,7 +300,6 @@ jobs:
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
- version
- gather-artifacts
- build-debian-src
- package-pio-deps-native-tft
@@ -374,36 +312,44 @@ jobs:
with:
python-version: 3.x
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
id: version
env:
BUILD_LOCATION: local
- name: Create release
uses: softprops/action-gh-release@v2
id: create_release
with:
draft: true
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
name: Meshtastic Firmware ${{ steps.version.outputs.long }} Alpha
tag_name: v${{ steps.version.outputs.long }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
uses: actions/download-artifact@v4
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
pattern: firmware-debian-${{ steps.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v4
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
pattern: platformio-deps-native-tft-${{ steps.version.outputs.long }}
merge-multiple: true
path: ./output/pio-deps-native-tft
- name: Zip Linux sources
working-directory: output
run: |
zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src
zip -9 -r ./platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip ./pio-deps-native-tft
# For diagnostics
- name: Display structure of downloaded files
@@ -413,8 +359,8 @@ jobs:
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd-${{ steps.version.outputs.deb }}-src.zip
gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -422,18 +368,10 @@ jobs:
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32]
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-artifacts, version]
needs: [release-artifacts]
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -443,9 +381,13 @@ jobs:
with:
python-version: 3.x
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- uses: actions/download-artifact@v4
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
pattern: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}
merge-multiple: true
path: ./output
@@ -458,16 +400,16 @@ jobs:
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v4
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip
merge-multiple: true
path: ./elfs
- name: Zip debug elfs
run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./elfs
# For diagnostics
- name: Display structure of downloaded files
@@ -477,18 +419,17 @@ jobs:
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
gh release upload v${{ steps.version.outputs.long }} ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip
gh release upload v${{ steps.version.outputs.long }} ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-firmware:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-firmware, version]
needs: [release-firmware]
env:
targets: |-
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
targets: esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,stm32
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -498,9 +439,13 @@ jobs:
with:
python-version: 3.x
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- uses: actions/download-artifact@v4
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
pattern: firmware-{${{ env.targets }}}-${{ steps.version.outputs.long }}
merge-multiple: true
path: ./publish
@@ -514,9 +459,9 @@ jobs:
external_repository: meshtastic/meshtastic.github.io
publish_branch: master
publish_dir: ./publish
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ steps.version.outputs.long }}
keep_files: true
user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com
commit_message: ${{ needs.version.outputs.long }}
commit_message: ${{ steps.version.outputs.long }}
enable_jekyll: true

View File

@@ -8,7 +8,6 @@ permissions: read-all
jobs:
trunk_check:
if: github.repository == 'meshtastic/firmware'
name: Trunk Check and Upload
runs-on: ubuntu-24.04
@@ -22,7 +21,6 @@ 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

View File

@@ -1,24 +0,0 @@
name: Check PR Labels
on:
pull_request:
types: [opened, edited, labeled, unlabeled, synchronize, reopened]
permissions:
pull-requests: read
contents: read
jobs:
check-label:
runs-on: ubuntu-24.04
steps:
- name: Check for PR labels
uses: actions/github-script@v7
with:
script: |
const labels = context.payload.pull_request.labels.map(label => label.name);
const requiredLabels = ['bugfix', 'enhancement', 'hardware-support', 'dependencies', 'submodules', 'github_actions', 'trunk'];
const hasRequiredLabel = labels.some(label => requiredLabels.includes(label));
if (!hasRequiredLabel) {
core.setFailed(`PR must have at least one of the following labels before it can be merged: ${requiredLabels.join(', ')}.`);
}

View File

@@ -20,11 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
series:
- jammy # 22.04
- noble # 24.04
- plucky # 25.04
# - questing # 25.10
series: [plucky, oracular, noble, jammy]
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: |-
@@ -103,9 +99,8 @@ jobs:
with:
base: ${{ github.event.repository.default_branch }}
branch: create-pull-request/bump-version
labels: github_actions
title: Bump release version
commit-message: Automated version bumps
commit-message: automated bumps
add-paths: |
version.properties
debian/changelog

View File

@@ -13,7 +13,6 @@ permissions:
jobs:
semgrep-full:
if: github.repository == 'meshtastic/firmware'
runs-on: ubuntu-24.04
container:
image: semgrep/semgrep

View File

@@ -11,7 +11,6 @@ permissions:
jobs:
stale_issues:
if: github.repository == 'meshtastic/firmware'
name: Close Stale Issues
runs-on: ubuntu-latest

View File

@@ -143,7 +143,7 @@ jobs:
merge-multiple: true
- name: Test Report
uses: dorny/test-reporter@v2.1.1
uses: dorny/test-reporter@v2.1.0
with:
name: PlatformIO Tests
path: testreport.xml

View File

@@ -12,11 +12,9 @@ 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

View File

@@ -34,9 +34,7 @@ jobs:
uses: peter-evans/create-pull-request@v7
with:
branch: create-pull-request/update-protobufs
labels: submodules
title: Update protobufs and classes
commit-message: Update protobufs
add-paths: |
protobufs
src/mesh

View File

@@ -8,15 +8,15 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.451
- renovate@41.40.0
- checkov@3.2.447
- renovate@41.17.2
- prettier@3.6.2
- trufflehog@3.90.1
- trufflehog@3.89.2
- yamllint@1.37.1
- bandit@1.8.6
- trivy@0.64.1
- bandit@1.8.5
- trivy@0.64.0
- taplo@0.9.3
- ruff@0.12.4
- ruff@0.12.1
- isort@6.0.1
- markdownlint@0.45.0
- oxipng@9.1.5
@@ -28,7 +28,7 @@ lint:
- shellcheck@0.10.0
- black@25.1.0
- git-diff-check
- gitleaks@8.28.0
- gitleaks@8.27.2
- clang-format@16.0.3
ignore:
- linters: [ALL]

View File

@@ -54,8 +54,8 @@ lib_deps =
h2zero/NimBLE-Arduino@^1.4.3
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
https://github.com/lewisxhe/XPowersLib/archive/v0.3.0.zip
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.0
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

View File

@@ -2,7 +2,7 @@
[portduino_base]
platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
https://github.com/meshtastic/platform-native/archive/6cb7a455b440dd0738e8ed74a18136ed5cf7ea63.zip
https://github.com/meshtastic/platform-native/archive/681ee029207e9fd040afa223df6e54074cbbe084.zip
framework = arduino
build_src_filter =
@@ -30,8 +30,6 @@ lib_deps =
lovyan03/LovyanGFX@^1.2.0
# renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
adafruit/Adafruit seesaw Library@1.7.9
build_flags =
${arduino_base.build_flags}
@@ -39,7 +37,7 @@ build_flags =
-Isrc/platform/portduino
-DRADIOLIB_EEPROM_UNSUPPORTED
-DPORTDUINO_LINUX_HARDWARE
-DHAS_UDP_MULTICAST=1
-DHAS_UDP_MULTICAST
-lpthread
-lstdc++fs
-lbluetooth
@@ -50,7 +48,4 @@ build_flags =
-std=gnu17
-std=c++17
lib_ignore =
Adafruit NeoPixel
Adafruit ST7735 and ST7789 Library
SD
lib_ignore = Adafruit NeoPixel

View File

@@ -2,7 +2,7 @@
extends = arduino_base
platform =
# renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32
platformio/ststm32@19.3.0
platformio/ststm32@19.2.0
platform_packages =
# TODO renovate
platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip
@@ -23,20 +23,14 @@ build_flags =
-DMESHTASTIC_EXCLUDE_SCREEN=1
-DMESHTASTIC_EXCLUDE_MQTT=1
-DMESHTASTIC_EXCLUDE_BLUETOOTH=1
-DMESHTASTIC_EXCLUDE_GPS=1
-DMESHTASTIC_EXCLUDE_WIFI=1
-DMESHTASTIC_EXCLUDE_TZ=1 ; Exclude TZ to save some flash space.
-DSERIAL_RX_BUFFER_SIZE=256 ; For GPS - the default of 64 is too small.
-DHAS_SCREEN=0 ; Always disable screen for STM32, it is not supported.
-DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF ; This is REQUIRED for at least traceroute debug prints - without it the length ends up uninitialized.
-DDEBUG_MUTE ; You can #undef DEBUG_MUTE in certain source files if you need the logs.
;-DDEBUG_MUTE
-fmerge-all-constants
-ffunction-sections
-fdata-sections
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_LR11X0=1
-DHAL_DAC_MODULE_ONLY
-DHAL_RNG_MODULE_ENABLED
build_src_filter =
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/RemoteHardwareModule.cpp> -<platform/nrf52> -<platform/portduino> -<platform/rp2xx0> -<mesh/raspihttp>
@@ -53,4 +47,4 @@ lib_deps =
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip
lib_ignore =
OneButton
mathertel/OneButton@2.6.1

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg install -e $1
platformio pkg update -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ elif (echo $2 | grep -q "nrf52"); then
elif (echo $2 | grep -q "stm32"); then
bin/build-stm32.sh $1
elif (echo $2 | grep -q "rpi2040"); then
bin/build-rp2xx0.sh $1
bin/build-rpi2040.sh $1
else
echo "Unknown target $2"
exit 1

View File

@@ -25,7 +25,7 @@ mkdir -p $OUTDIR/
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
pio pkg install --environment "$PIO_ENV" || platformioFailed
pio pkg update --environment "$PIO_ENV" || platformioFailed
pio run --environment "$PIO_ENV" || platformioFailed
cp ".pio/build/$PIO_ENV/program" "$OUTDIR/meshtasticd_linux_$(uname -m)"
cp bin/native-install.* $OUTDIR

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg install -e $1
platformio pkg update -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg install -e $1
platformio pkg update -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg install -e $1
platformio pkg update -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -199,10 +199,6 @@ HostMetrics:
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
Config:
# DisplayMode: TWOCOLOR # uncomment to force BaseUI
# DisplayMode: COLOR # uncomment to force MUI
General:
MaxNodes: 200
MaxMessageQueue: 100

View File

@@ -1,52 +0,0 @@
# https://www.waveshare.com/pico-lora-sx1262-868m.htm
# http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero-3.html
#
# See Orange Pi Zero3 manual, chapter 3.16, page 124 for 26-pin header pinout
#
# Pin Connection
# Waveshare Orange Pi Zero3
# 36 3.3V 17
# 15 MOSI 19
# 16 MISO 21
# 14 CLK 23
# 38 GND 25
# 4 BUSY 18
# 20 RESET 22
# 5 CS 24
# 26 DIO1/IRQ 26
Lora:
Module: sx1262 # Waveshare Raspberry Pico Lora module
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
# Specify either the spidev1_1 or the CS below, not both!
# On DietPi Linux, when using the user overlay dietpi-spi1_1.dtbo, CS will be configured with spidev1.1
spidev: spidev1.1 # See Orange Pi Zero3 manual, chapter 3.18.3, page 130
# CS: # CS PIN_24 -> chip 1, line 233
# pin: 24
# gpiochip: 1
# line: 233
SCK: # SCK PIN_23 -> chip 1, line 230
pin: 23
gpiochip: 1
line: 230
Busy: # BUSY PIN_18 -> chip 1, line 78
pin: 18
gpiochip: 1
line: 78
MOSI: # MOSI PIN_19 -> chip 1, line 231
pin: 19
gpiochip: 1
line: 231
MISO: # MISO PIN_21 -> chip 1, line 232
pin: 21
gpiochip: 1
line: 232
Reset: # NRST PIN_22 -> chip 1, line 71
pin: 22
gpiochip: 1
line: 71
IRQ: # DIO1 PIN_26 -> chip 1, line 74
pin: 26
gpiochip: 1
line: 74

View File

@@ -7,7 +7,12 @@ MCU=""
# Variant groups
BIGDB_8MB=(
"picomputer-s3"
# 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"
"unphone"
"seeed-sensecap-indicator"
"crowpanel-esp32s3"

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
CHANGE_MODE=false
@@ -31,16 +31,17 @@ EOF
}
# Check for --change-mode and remove it from arguments
NEW_ARGS=()
NEW_ARGS=""
for arg in "$@"; do
if [ "$arg" = "--change-mode" ]; then
CHANGE_MODE=true
else
NEW_ARGS+=("$arg")
NEW_ARGS="$NEW_ARGS \"\$arg\""
fi
done
set -- "${NEW_ARGS[@]}"
# Reset positional parameters to filtered list
eval set -- $NEW_ARGS
while getopts ":hp:P:f:" opt; do
case "${opt}" in

View File

@@ -2,11 +2,13 @@
"""Generate the CI matrix."""
import configparser
import json
import os
import sys
import random
import re
from platformio.project.config import ProjectConfig
rootdir = "variants/"
options = sys.argv[1:]
@@ -14,59 +16,36 @@ outlist = []
if len(options) < 1:
print(json.dumps(outlist))
exit(1)
exit()
cfg = ProjectConfig.get_instance()
pio_envs = cfg.envs()
# Gather all PlatformIO environments for filtering later
all_envs = []
for pio_env in pio_envs:
env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags')
env_platform = None
for flag in env_build_flags:
# Extract the platform from the build flags
# Example flag: -I variants/esp32s3/heltec-v3
match = re.search(r"-I\s?variants/([^/]+)", flag)
if match:
env_platform = match.group(1)
break
# Intentionally fail if platform cannot be determined
if not env_platform:
print(f"Error: Could not determine platform for environment '{pio_env}'")
exit(1)
# Store env details as a dictionary, and add to 'all_envs' list
env = {
'name': pio_env,
'platform': env_platform,
'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None),
'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False))
}
all_envs.append(env)
# Filter outputs based on options
# Check is mutually exclusive with other options (except 'pr')
if "check" in options:
for env in all_envs:
if env['board_check']:
if "pr" in options:
if env['board_level'] == 'pr':
outlist.append(env['name'])
for subdir, dirs, files in os.walk(rootdir):
for file in files:
if file == "platformio.ini":
config = configparser.ConfigParser()
config.read(subdir + "/" + file)
for c in config.sections():
if c.startswith("env:"):
section = config[c].name[4:]
if "extends" in config[config[c].name]:
if options[0] + "_base" in config[config[c].name]["extends"]:
if "board_level" in config[config[c].name]:
if (
config[config[c].name]["board_level"] == "extra"
) & ("extra" in options):
outlist.append(section)
else:
outlist.append(env['name'])
# Filter (non-check) builds by platform
outlist.append(section)
# Add the TFT variants if the base variant is selected
elif section.replace("-tft", "") in outlist and config[config[c].name].get("board_level") != "extra":
outlist.append(section)
elif section.replace("-inkhud", "") in outlist and config[config[c].name].get("board_level") != "extra":
outlist.append(section)
if "board_check" in config[config[c].name]:
if (config[config[c].name]["board_check"] == "true") & (
"check" in options
):
outlist.append(section)
if ("quick" in options) & (len(outlist) > 3):
print(json.dumps(random.sample(outlist, 3)))
else:
for env in all_envs:
if options[0] == env['platform']:
# Always include board_level = 'pr'
if env['board_level'] == 'pr':
outlist.append(env['name'])
# Include board_level = 'extra' when requested
elif "extra" in options and env['board_level'] == "extra":
outlist.append(env['name'])
# If no board level is specified, include in release builds (not PR)
elif "pr" not in options and not env['board_level']:
outlist.append(env['name'])
# Return as a JSON list
print(json.dumps(outlist))

View File

@@ -87,15 +87,6 @@
</screenshots>
<releases>
<release version="2.7.5" date="2025-08-09">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.5</url>
</release>
<release version="2.7.4" date="2025-07-19">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.4</url>
</release>
<release version="2.7.3" date="2025-07-10">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.3</url>
</release>
<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>

View File

@@ -3,7 +3,6 @@
# trunk-ignore-all(flake8/F821): For SConstruct imports
import sys
from os.path import join
import subprocess
import json
import re
@@ -93,17 +92,6 @@ prefsLoc = projenv["PROJECT_DIR"] + "/version.properties"
verObj = readProps(prefsLoc)
print("Using meshtastic platformio-custom.py, firmware version " + verObj["long"] + " on " + env.get("PIOENV"))
# get repository owner if git is installed
try:
r_owner = (
subprocess.check_output(["git", "config", "--get", "remote.origin.url"])
.decode("utf-8")
.strip().split("/")
)
repo_owner = r_owner[-2] + "/" + r_owner[-1].replace(".git", "")
except subprocess.CalledProcessError:
repo_owner = "unknown"
jsonLoc = env["PROJECT_DIR"] + "/userPrefs.jsonc"
with open(jsonLoc) as f:
jsonStr = re.sub("//.*","", f.read(), flags=re.MULTILINE)
@@ -129,7 +117,6 @@ flags = [
"-DAPP_VERSION=" + verObj["long"],
"-DAPP_VERSION_SHORT=" + verObj["short"],
"-DAPP_ENV=" + env.get("PIOENV"),
"-DAPP_REPO=" + repo_owner,
] + pref_flags
print ("Using flags:")

View File

@@ -10,8 +10,7 @@
"hwids": [
["0x239A", "0x4405"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x2886", "0x1667"]
["0x239A", "0x002A"]
],
"usb_product": "HT-n5262",
"mcu": "nrf52840",

View File

@@ -1,43 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_qspi",
"partitions": "default_16MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "LilyGo T-Deck Pro S3 (16M Flash 8M QSPI PSRAM )",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"monitor": {
"speed": 115200
},
"url": "https://lilygo.cc/products/t-deck-pro",
"vendor": "LilyGo"
}

13
debian/changelog vendored
View File

@@ -1,4 +1,4 @@
meshtasticd (2.7.5.0) UNRELEASED; urgency=medium
meshtasticd (2.7.2.0) UNRELEASED; urgency=medium
[ Austin Lane ]
* Initial packaging
@@ -28,13 +28,4 @@ meshtasticd (2.7.5.0) UNRELEASED; urgency=medium
[ ]
* GitHub Actions Automatic version bump
[ Ubuntu ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Sat, 09 Aug 2025 12:46:53 +0000
-- <github-actions[bot]@users.noreply.github.com> Fri, 04 Jul 2025 11:58:01 +0000

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh
# postinst script for meshtasticd
#
# see: dh_installdeb(1)

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh
# postrm script for meshtasticd
#
# see: dh_installdeb(1)

View File

@@ -6,8 +6,7 @@ default_envs = tbeam
extra_configs =
arch/*/*.ini
variants/*/*/platformio.ini
variants/*/diy/*/platformio.ini
variants/*/platformio.ini
src/graphics/niche/InkHUD/PlatformioConfig.ini
description = Meshtastic
@@ -105,18 +104,18 @@ lib_deps =
[radiolib_base]
lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
jgromes/RadioLib@7.2.1
jgromes/RadioLib@7.2.0
[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/0cd108ff783539e41ef38258ba2784ab3b1bdc97.zip
https://github.com/meshtastic/device-ui/archive/8c7092c73425adfda1aac8c6960df06cd85f6d92.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
adafruit/Adafruit BusIO@1.17.2
adafruit/Adafruit BusIO@1.17.1
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library
@@ -130,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.3
adafruit/Adafruit INA260 Library@1.5.2
# 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
@@ -178,7 +177,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X
adafruit/Adafruit MAX1704X@1.0.3
# renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library
adafruit/Adafruit SHTC3 Library@1.0.2
adafruit/Adafruit SHTC3 Library@1.0.1
# renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X
adafruit/Adafruit LPS2X@2.0.6
# renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library

View File

@@ -20,11 +20,6 @@
#include "meshUtils.h"
#include "sleep.h"
#if defined(ARCH_PORTDUINO)
#include "api/WiFiServerAPI.h"
#include "input/LinuxInputImpl.h"
#endif
// Working USB detection for powered/charging states on the RAK platform
#ifdef NRF_APM
#include "nrfx_power.h"
@@ -125,15 +120,6 @@ NullSensor max17048Sensor;
RAK9154Sensor rak9154Sensor;
#endif
#ifdef HAS_PPM
// note: XPOWERS_CHIP_XXX must be defined in variant.h
#include <XPowersLib.h>
#endif
#ifdef HAS_BQ27220
#include "bq27220.h"
#endif
#ifdef HAS_PMU
XPowersLibInterface *PMU = NULL;
#else
@@ -679,8 +665,6 @@ bool Power::setup()
found = true;
} else if (lipoInit()) {
found = true;
} else if (lipoChargerInit()) {
found = true;
} else if (analogInit()) {
found = true;
}
@@ -695,61 +679,9 @@ bool Power::setup()
return found;
}
void Power::powerCommandsCheck()
{
if (rebootAtMsec && millis() > rebootAtMsec) {
LOG_INFO("Rebooting");
reboot();
}
if (shutdownAtMsec && millis() > shutdownAtMsec) {
shutdownAtMsec = 0;
shutdown();
}
}
void Power::reboot()
{
notifyReboot.notifyObservers(NULL);
#if defined(ARCH_ESP32)
ESP.restart();
#elif defined(ARCH_NRF52)
NVIC_SystemReset();
#elif defined(ARCH_RP2040)
rp2040.reboot();
#elif defined(ARCH_PORTDUINO)
deInitApiServer();
if (aLinuxInputImpl)
aLinuxInputImpl->deInit();
SPI.end();
Wire.end();
Serial1.end();
if (screen) {
delete screen;
screen = nullptr;
}
LOG_DEBUG("final reboot!");
::reboot();
#elif defined(ARCH_STM32WL)
HAL_NVIC_SystemReset();
#else
rebootAtMsec = -1;
LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied");
#endif
}
void Power::shutdown()
{
#if HAS_SCREEN
if (screen) {
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
}
#endif
#if !defined(ARCH_STM32WL)
playShutdownMelody();
#endif
nodeDB->saveToDisk();
LOG_INFO("Shutting Down");
#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
#ifdef PIN_LED1
@@ -761,11 +693,7 @@ void Power::shutdown()
#ifdef PIN_LED3
ledOff(PIN_LED3);
#endif
doDeepSleep(DELAY_FOREVER, false, true);
#elif defined(ARCH_PORTDUINO)
exit(EXIT_SUCCESS);
#else
LOG_WARN("FIXME implement shutdown for this platform");
doDeepSleep(DELAY_FOREVER, false, false);
#endif
}
@@ -1309,144 +1237,3 @@ bool Power::lipoInit()
return false;
}
#endif
#if defined(HAS_PPM) && HAS_PPM
/**
* Adapter class for BQ25896/BQ27220 Lipo battery charger.
*/
class LipoCharger : public HasBatteryLevel
{
private:
XPowersPPM *ppm = nullptr;
BQ27220 *bq = nullptr;
public:
/**
* Init the I2C BQ25896 Lipo battery charger
*/
bool runOnce()
{
if (ppm == nullptr) {
ppm = new XPowersPPM;
bool result = ppm->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
if (result) {
LOG_INFO("PPM BQ25896 init succeeded");
// Set the minimum operating voltage. Below this voltage, the PPM will protect
// ppm->setSysPowerDownVoltage(3100);
// Set input current limit, default is 500mA
// ppm->setInputCurrentLimit(800);
// Disable current limit pin
// ppm->disableCurrentLimitPin();
// Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV
ppm->setChargeTargetVoltage(4288);
// Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA
// ppm->setPrechargeCurr(64);
// The premise is that limit pin is disabled, or it will
// only follow the maximum charging current set by limit pin.
// Set the charging current , Range:0~5056mA ,step:64mA
ppm->setChargerConstantCurr(1024);
// To obtain voltage data, the ADC must be enabled first
ppm->enableMeasure();
// Turn on charging function
// If there is no battery connected, do not turn on the charging function
ppm->enableCharge();
} else {
LOG_WARN("PPM BQ25896 init failed");
delete ppm;
ppm = nullptr;
return false;
}
}
if (bq == nullptr) {
bq = new BQ27220;
bq->setDefaultCapacity(BQ27220_DESIGN_CAPACITY);
bool result = bq->init();
if (result) {
LOG_DEBUG("BQ27220 design capacity: %d", bq->getDesignCapacity());
LOG_DEBUG("BQ27220 fullCharge capacity: %d", bq->getFullChargeCapacity());
LOG_DEBUG("BQ27220 remaining capacity: %d", bq->getRemainingCapacity());
return true;
} else {
LOG_WARN("BQ27220 init failed");
delete bq;
bq = nullptr;
return false;
}
}
return false;
}
/**
* Battery state of charge, from 0 to 100 or -1 for unknown
*/
virtual int getBatteryPercent() override
{
return -1;
// return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated
}
/**
* The raw voltage of the battery in millivolts, or NAN if unknown
*/
virtual uint16_t getBattVoltage() override { return bq->getVoltage(); }
/**
* return true if there is a battery installed in this unit
*/
virtual bool isBatteryConnect() override { return ppm->getBattVoltage() > 0; }
/**
* return true if there is an external power source detected
*/
virtual bool isVbusIn() override { return ppm->getVbusVoltage() > 0; }
/**
* return true if the battery is currently charging
*/
virtual bool isCharging() override
{
bool isCharging = ppm->isCharging();
if (isCharging) {
LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull());
} else {
if (!ppm->isVbusIn()) {
LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity());
}
}
return isCharging;
}
};
LipoCharger lipoCharger;
/**
* Init the Lipo battery charger
*/
bool Power::lipoChargerInit()
{
bool result = lipoCharger.runOnce();
LOG_DEBUG("Power::lipoChargerInit lipo sensor is %s", result ? "ready" : "not ready yet");
if (!result)
return false;
batteryLevel = &lipoCharger;
return true;
}
#else
/**
* The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel
*/
bool Power::lipoChargerInit()
{
return false;
}
#endif

View File

@@ -72,7 +72,7 @@ extern Power *power;
static void shutdownEnter()
{
LOG_DEBUG("State: SHUTDOWN");
shutdownAtMsec = millis();
power->shutdown();
}
#include "error.h"

View File

@@ -47,6 +47,10 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
playComboTune(); // Ping sent feedback
break;
case INPUT_BROKER_SHUTDOWN:
playShutdownMelody(); // Shutdown feedback
break;
default:
// For other events, check if it's a printable character
if (event->kbchar >= 32 && event->kbchar <= 126) {
@@ -65,7 +69,10 @@ int32_t BuzzerFeedbackThread::runOnce()
// This thread is primarily event-driven, but we can use runOnce
// for any periodic tasks if needed in the future
if (needsUpdate) {
needsUpdate = false;
// Could add any periodic processing here
}
// Run every 100ms when active, less frequently when idle
return needsUpdate ? 100 : 1000;

View File

@@ -150,12 +150,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Define if screen should be mirrored left to right
// #define SCREEN_MIRROR
// I2C Keyboards (M5Stack, RAK14004, T-Deck, T-Deck Pro, T-Lora Pager, CardKB, BBQ10, MPR121, TCA8418)
// I2C Keyboards (M5Stack, RAK14004, T-Deck)
#define CARDKB_ADDR 0x5F
#define TDECK_KB_ADDR 0x55
#define BBQ10_KB_ADDR 0x1F
#define MPR121_KB_ADDR 0x5A
#define TCA8418_KB_ADDR 0x34
// -----------------------------------------------------------------------------
// SENSOR
@@ -194,11 +193,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MLX90614_ADDR_DEF 0x5A
#define CGRADSENS_ADDR 0x66
#define LTR390UV_ADDR 0x53
#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418_KB
#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418
#define PCT2075_ADDR 0x37
#define BQ27220_ADDR 0x55 // same address as TDECK_KB
#define BQ25896_ADDR 0x6B
#define LTR553ALS_ADDR 0x23
// -----------------------------------------------------------------------------
// ACCELEROMETER
@@ -212,7 +208,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BMX160_ADDR 0x69
#define ICM20948_ADDR 0x69
#define ICM20948_ADDR_ALT 0x68
#define BHI260AP_ADDR 0x28
#define BMM150_ADDR 0x13
// -----------------------------------------------------------------------------
@@ -235,7 +230,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Touchscreen
// -----------------------------------------------------------------------------
#define FT6336U_ADDR 0x48
#define CST328_ADDR 0x1A
// -----------------------------------------------------------------------------
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)

View File

@@ -74,12 +74,7 @@ class ScanI2C
RAK12035,
TCA8418KB,
PCT2075,
CST328,
BQ25896,
BQ27220,
LTR553ALS,
BHI260AP,
BMM150
BMM150,
} DeviceType;
// typedef uint8_t DeviceAddress;

View File

@@ -184,13 +184,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = RTC_RV3028;
logFoundDevice("RV3028", (uint8_t)addr.address);
rtc.initI2C(*i2cBus);
// Update RTC EEPROM settings, if necessary
if (rtc.readEEPROMRegister(0x35) != 0x07) {
rtc.writeEEPROMRegister(0x35, 0x07); // no Clkout
}
if (rtc.readEEPROMRegister(0x37) != 0xB4) {
rtc.writeEEPROMRegister(0x37, 0xB4);
}
rtc.writeToRegister(0x35, 0x07); // no Clkout
rtc.writeToRegister(0x37, 0xB4);
break;
#endif
@@ -211,17 +206,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
case TDECK_KB_ADDR:
// Do we have the T-Deck keyboard or the T-Deck Pro battery sensor?
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x04), 1);
if (registerValue != 0) {
logFoundDevice("BQ27220", (uint8_t)addr.address);
type = BQ27220;
} else {
logFoundDevice("TDECKKB", (uint8_t)addr.address);
type = TDECKKB;
}
break;
SCAN_SIMPLE_CASE(TDECK_KB_ADDR, TDECKKB, "T-Deck keyboard", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address);
@@ -411,12 +396,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("BQ24295", (uint8_t)addr.address);
break;
}
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x14), 1); // get ID
if ((registerValue & 0b00000011) == 0b00000010) {
type = BQ25896;
logFoundDevice("BQ25896", (uint8_t)addr.address);
break;
}
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); // get ID
if (registerValue == 0x6A) {
type = LSM6DS3;
@@ -468,9 +447,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(LTR553ALS_ADDR, LTR553ALS, "LTR553ALS", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
#ifdef HAS_TPS65233

View File

@@ -39,9 +39,11 @@ template <typename T, std::size_t N> std::size_t array_count(const T (&)[N])
return N;
}
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
#if defined(GPS_SERIAL_PORT)
HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if defined(SENSECAP_INDICATOR)
FakeUART *GPS::_serial_gps = FakeSerial;
#elif defined(RAK2560)
HardwareSerial *GPS::_serial_gps = &Serial2;
#else
HardwareSerial *GPS::_serial_gps = &Serial1;
#endif
@@ -643,16 +645,8 @@ bool GPS::setup()
delay(250);
} else if (IS_ONE_OF(gnssModel, GNSS_MODEL_AG3335, GNSS_MODEL_AG3352)) {
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_IN ||
config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_NP_865) {
_serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC
// GPS GLONASS GALILEO BDS QZSS NAVIC
// 1 0 1 0 0 1
} else {
_serial_gps->write("$PAIR066,1,1,1,1,0,0*3A\r\n"); // Enable GPS+GLONASS+GALILEO+BDS
// GPS GLONASS GALILEO BDS QZSS NAVIC
// 1 1 1 1 0 0
}
// Configure NMEA (sentences will output once per fix)
_serial_gps->write("$PAIR062,0,1*3F\r\n"); // GGA ON
_serial_gps->write("$PAIR062,1,0*3F\r\n"); // GLL OFF
@@ -1084,6 +1078,7 @@ void GPS::publishUpdate()
int32_t GPS::runOnce()
{
#if !defined(SENSECAP_INDICATOR)
if (!GPSInitFinished) {
if (!_serial_gps || config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
LOG_INFO("GPS set to not-present. Skip probe");
@@ -1099,6 +1094,7 @@ int32_t GPS::runOnce()
GPSInitFinished = true;
publishUpdate();
}
#endif
// Repeaters have no need for GPS
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
@@ -1417,8 +1413,10 @@ GPS *GPS::createGps()
if (!settingsMap[has_gps])
return nullptr;
#endif
#if !defined(SENSECAP_INDICATOR)
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
return nullptr;
#endif
GPS *new_gps = new GPS;
new_gps->rx_gpio = _rx_gpio;
@@ -1511,7 +1509,7 @@ bool GPS::lookForTime()
#ifdef GNSS_AIROHA
uint8_t fix = reader.fixQuality();
if (fix >= 1 && fix <= 5) {
if (fix > 0) {
if (lastFixStartMsec > 0) {
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false;
@@ -1544,10 +1542,7 @@ 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());
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) {
// Clear the GPS buffer if we got an invalid time
clearBuffer();
}
perhapsSetRTC(RTCQualityGPS, t);
return true;
} else
return false;
@@ -1566,7 +1561,7 @@ bool GPS::lookForLocation()
#ifdef GNSS_AIROHA
if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) {
uint8_t fix = reader.fixQuality();
if (fix >= 1 && fix <= 5) {
if (fix > 0) {
if (lastFixStartMsec > 0) {
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false;

View File

@@ -11,6 +11,10 @@
#include "input/UpDownInterruptImpl1.h"
#include "modules/PositionModule.h"
#ifdef SENSECAP_INDICATOR
#include "mesh/comms/FakeUART.h"
#endif
// Allow defining the polarity of the ENABLE output. default is active high
#ifndef GPS_EN_ACTIVE
#define GPS_EN_ACTIVE 1
@@ -188,7 +192,9 @@ class GPS : private concurrency::OSThread
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
/** If !NULL we will use this serial port to construct our GPS */
#if defined(ARCH_RP2040)
#if defined(SENSECAP_INDICATOR)
static FakeUART *_serial_gps;
#elif defined(ARCH_RP2040)
static SerialUART *_serial_gps;
#else
static HardwareSerial *_serial_gps;

View File

@@ -105,7 +105,7 @@ void readFromRTC()
*
* If we haven't yet set our RTC this boot, set it from a GPS derived time
*/
RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
{
static uint32_t lastSetMsec = 0;
uint32_t now = millis();
@@ -113,7 +113,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
#ifdef BUILD_EPOCH
if (tv->tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime;
return false;
}
#endif
@@ -184,9 +184,9 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
readFromRTC();
#endif
return RTCSetResultSuccess;
return true;
} else {
return RTCSetResultNotSet; // RTC was already set with a higher quality time
return false;
}
}
@@ -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.
*/
RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
bool 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
@@ -227,18 +227,11 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
struct timeval tv;
tv.tv_sec = res;
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime;
}
#endif
// 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 RTCSetResultInvalidTime;
return false;
} else {
return perhapsSetRTC(q, &tv);
}

View File

@@ -22,22 +22,13 @@ 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
RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t);
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
bool perhapsSetRTC(RTCQuality q, struct tm &t);
/// Return a string name for the quality
const char *RtcName(RTCQuality quality);

View File

@@ -6,10 +6,6 @@
#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.
@@ -151,21 +147,6 @@ bool EInkDisplay::connect()
#else
adafruitDisplay->setRotation(3);
#endif
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
}
#elif defined(ELECROW_ThinkNode_M5)
{
// Start HSPI
hspi = new SPIClass(HSPI);
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
adafruitDisplay->init();
adafruitDisplay->setRotation(4);
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
}
#elif defined(MESHLINK)
@@ -193,8 +174,9 @@ bool EInkDisplay::connect()
}
}
#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)
#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)
{
// Start HSPI
hspi = new SPIClass(HSPI);
@@ -221,7 +203,7 @@ bool EInkDisplay::connect()
adafruitDisplay->setRotation(0);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
}
#elif defined(M5_COREINK) || defined(T_DECK_PRO)
#elif defined(M5_COREINK)
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
@@ -250,23 +232,6 @@ 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;

View File

@@ -5,10 +5,6 @@
#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.
*
@@ -67,20 +63,13 @@ class EInkDisplay : public OLEDDisplay
// Connect to the display
virtual bool connect() override;
#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
// AdafruitGFX display object - 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) || \
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || defined(ELECROW_ThinkNode_M5)
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
SPIClass *hspi = NULL;
#endif

View File

@@ -1,135 +0,0 @@
// 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;
};

View File

@@ -21,7 +21,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Screen.h"
#include "NodeDB.h"
#include "PowerMon.h"
#include "Throttle.h"
#include "configuration.h"
@@ -45,6 +44,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#include "FSCommon.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RadioLibInterface.h"
#include "error.h"
#include "gps/GeoCoord.h"
@@ -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(uint32_t)> bannerCallback)
void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback)
{
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
@@ -196,6 +196,7 @@ 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
@@ -293,13 +294,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 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);
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);
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);
if (r <= 255 && g <= 255 && b <= 255) {
TFT_MESH = COLOR565(r, g, b);
}
}
@@ -312,8 +313,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);
#endif
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#endif
#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);
@@ -386,15 +387,13 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#ifdef T_WATCH_S3
PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif
#ifdef HELTEC_TRACKER_V1_X
uint8_t tft_vext_enabled = digitalRead(VEXT_ENABLE);
#endif
#if !ARCH_PORTDUINO
dispdev->displayOn();
#endif
#ifdef ELECROW_ThinkNode_M5
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
#endif
#if defined(ST7789_CS) && \
!defined(M5STACK) // set display brightness when turning on screens. Just moved function from TFTDisplay to here.
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
@@ -402,7 +401,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
dispdev->displayOn();
#ifdef HELTEC_TRACKER_V1_X
// If the TFT VEXT power is not enabled, initialize the UI.
if (!tft_vext_enabled) {
ui->init();
}
#endif
#ifdef USE_ST7789
pinMode(VTFT_CTRL, OUTPUT);
@@ -429,11 +431,6 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
digitalWrite(PIN_EINK_EN, LOW);
}
#endif
#ifdef ELECROW_ThinkNode_M5
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
#endif
dispdev->displayOff();
#ifdef USE_ST7789
SPI1.end();
@@ -588,7 +585,7 @@ void Screen::setup()
touchScreenImpl1->init();
}
}
#elif HAS_TOUCHSCREEN && !defined(USE_EINK)
#elif HAS_TOUCHSCREEN
touchScreenImpl1 =
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
touchScreenImpl1->init();
@@ -644,11 +641,6 @@ void Screen::forceDisplay(bool forceUiUpdate)
// Tell EInk class to update the display
static_cast<EInkDisplay *>(dispdev)->forceDisplay();
#else
// No delay between UI frame rendering
if (forceUiUpdate) {
setFastFramerate();
}
#endif
}
@@ -873,8 +865,6 @@ void Screen::setFrames(FrameFocus focus)
uint8_t previousFrameCount = framesetInfo.frameCount;
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
graphics::UIRenderer::rebuildFavoritedNodes();
LOG_DEBUG("Show standard frames");
showingNormalScreen = true;
@@ -954,6 +944,22 @@ 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;
@@ -963,7 +969,7 @@ void Screen::setFrames(FrameFocus focus)
#endif
// Beware of what changes you make in this code!
// We pass numframes into GetMeshModulesWithUIFrames() which is highly important!
// We pass numfames 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.
@@ -992,34 +998,6 @@ 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 (const 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);
@@ -1031,7 +1009,8 @@ 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) {
@@ -1268,12 +1247,8 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
devicestate.has_rx_text_message = true; // Needed to include the message frame
hasUnreadMessage = true; // Enables mail icon in the header
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
// Only wake/force display if the configuration allows it
if (shouldWakeOnReceivedMessage()) {
setOn(true); // Wake up the screen first
forceDisplay(); // Forces screen redraw
}
// === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
@@ -1344,7 +1319,7 @@ int Screen::handleInputEvent(const InputEvent *event)
setFastFramerate(); // Draw ASAP
#endif
if (NotificationRenderer::isOverlayBannerShowing()) {
NotificationRenderer::inEvent = *event;
NotificationRenderer::inEvent = event->inputEvent;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP
@@ -1384,12 +1359,9 @@ int Screen::handleInputEvent(const InputEvent *event)
menuHandler::clockMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
menuHandler::LoraRegionPicker();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
if (devicestate.rx_text_message.from) {
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage &&
devicestate.rx_text_message.from) {
menuHandler::messageResponseMenu();
} else {
menuHandler::textMessageBaseMenu();
}
} else if (framesetInfo.positions.firstFavorite != 255 &&
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
@@ -1441,23 +1413,3 @@ bool Screen::isOverlayBannerShowing()
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
#endif // HAS_SCREEN
bool shouldWakeOnReceivedMessage()
{
/*
The goal here is to determine when we do NOT wake up the screen on message received:
- Any ext. notifications are turned on
- If role is not client / client_mute
- If the battery level is very low
*/
if (moduleConfig.external_notification.enabled) {
return false;
}
if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
return false;
}
if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
return false;
}
return true;
}

View File

@@ -26,8 +26,6 @@ struct BannerOverlayOptions {
};
} // namespace graphics
bool shouldWakeOnReceivedMessage();
#if !HAS_SCREEN
#include "power.h"
namespace graphics
@@ -94,7 +92,6 @@ 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"
@@ -311,15 +308,9 @@ 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(uint32_t)> bannerCallback);
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> 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;

View File

@@ -206,7 +206,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
timeX = screenW - xOffset - timeStrWidth + 3;
// === Show Mail or Mute Icon to the Left of Time ===
int iconRightEdge = timeX - 2;
int iconRightEdge = timeX - 1;
bool showMail = false;

View File

@@ -186,7 +186,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
{
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
int line = 1;
// === Set Title, Blank for Clock
const char *titleStr = "";
// === Header ===
@@ -218,6 +218,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
hour %= 12;
if (hour == 0)
hour = 12;
bool isPM = hour >= 12;
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
} else {
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
@@ -229,8 +230,6 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
#ifdef T_WATCH_S3
float scale = 1.5;
#elif defined(CHATTER_2)
float scale = 1.1;
#else
float scale = 0.75;
if (isHighResolution) {
@@ -286,9 +285,6 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
int yOffset = (isHighResolution) ? 3 : 1;
#ifdef SENSECAP_INDICATOR
yOffset -= 3;
#endif
#ifdef T_DECK
yOffset -= 5;
#endif
if (config.display.use_12h_clock) {
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
@@ -366,7 +362,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// hour hand radius and y coordinate
int16_t hourHandRadius = radius * 0.35;
if (isHighResolution) {
hourHandRadius = radius * 0.55;
int16_t hourHandRadius = radius * 0.55;
}
int16_t hourHandNoonY = centerY - hourHandRadius;
@@ -385,7 +381,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
bool isPM = hour >= 12;
if (config.display.use_12h_clock) {
isPM = hour >= 12;
bool isPM = hour >= 12;
display->setFont(FONT_SMALL);
int yOffset = isHighResolution ? 1 : 0;
#ifdef USE_EINK

View File

@@ -412,9 +412,9 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
float freq = RadioLibInterface::instance->getFreq();
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
if (config.lora.channel_num == 0) {
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %smhz", freqStr);
} else {
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %smhz (%d)", freqStr, config.lora.channel_num);
}
size_t len = strlen(frequencyslot);
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
@@ -483,7 +483,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
}
// ****************************
// * System Screen *
// * Memory Screen *
// ****************************
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@@ -593,19 +593,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
}
line += 1;
char appversionstr[35];
snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION));
char appversionstr_formatted[40];
char *lastDot = strrchr(appversionstr, '.');
if (lastDot) {
size_t prefixLen = lastDot - appversionstr;
strncpy(appversionstr_formatted, appversionstr, prefixLen);
appversionstr_formatted[prefixLen] = '\0';
strncat(appversionstr_formatted, " (", sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
strncat(appversionstr_formatted, lastDot + 1, sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
strncat(appversionstr_formatted, ")", sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
appversionstr[sizeof(appversionstr) - 1] = '\0';
}
snprintf(appversionstr, sizeof(appversionstr), "Ver.: %s", optstr(APP_VERSION));
int textWidth = display->getStringWidth(appversionstr);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], appversionstr);

View File

@@ -13,10 +13,6 @@
#include "main.h"
#include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h"
#include "modules/KeyVerificationModule.h"
#include "modules/TraceRouteModule.h"
#include <functional>
extern uint16_t TFT_MESH;
@@ -54,14 +50,12 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
"PH_915",
"ANZ_433",
"KZ_433",
"KZ_863",
"NP_865",
"BR_902"};
"KZ_863"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Set the LoRa region";
bannerOptions.durationMs = duration;
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 27;
bannerOptions.optionsCount = 25;
bannerOptions.InitialSelected = 0;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) {
@@ -120,22 +114,6 @@ void menuHandler::TwelveHourPicker()
screen->showOverlayBanner(bannerOptions);
}
// Reusable confirmation prompt function
void menuHandler::showConfirmationBanner(const char *message, std::function<void()> onConfirm)
{
static const char *confirmOptions[] = {"No", "Yes"};
BannerOverlayOptions confirmBanner;
confirmBanner.message = message;
confirmBanner.optionsArrayPtr = confirmOptions;
confirmBanner.optionsCount = 2;
confirmBanner.bannerCallback = [onConfirm](int confirmSelected) -> void {
if (confirmSelected == 1) {
onConfirm();
}
};
screen->showOverlayBanner(confirmBanner);
}
void menuHandler::ClockFacePicker()
{
static const char *optionsArray[] = {"Back", "Digital", "Analog"};
@@ -150,15 +128,14 @@ void menuHandler::ClockFacePicker()
screen->runNow();
} else if (selected == Digital) {
uiconfig.is_clockface_analog = false;
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
screen->setFrames(Screen::FOCUS_CLOCK);
} else {
uiconfig.is_clockface_analog = true;
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
screen->setFrames(Screen::FOCUS_CLOCK);
}
};
bannerOptions.InitialSelected = uiconfig.is_clockface_analog ? 2 : 1;
screen->showOverlayBanner(bannerOptions);
}
@@ -172,7 +149,6 @@ void menuHandler::TZPicker()
"US/Mountain",
"US/Central",
"US/Eastern",
"BR/Brasilia",
"UTC",
"EU/Western",
"EU/"
@@ -187,7 +163,7 @@ void menuHandler::TZPicker()
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Pick Timezone";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 19;
bannerOptions.optionsCount = 17;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) {
menuHandler::menuQueue = menuHandler::clock_menu;
@@ -206,27 +182,25 @@ void menuHandler::TZPicker()
strncpy(config.device.tzdef, "CST6CDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef));
} else if (selected == 7) { // Eastern
strncpy(config.device.tzdef, "EST5EDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef));
} else if (selected == 8) { // Brazil
strncpy(config.device.tzdef, "BRT3", sizeof(config.device.tzdef));
} else if (selected == 9) { // UTC
strncpy(config.device.tzdef, "UTC0", sizeof(config.device.tzdef));
} else if (selected == 10) { // EU/Western
} else if (selected == 8) { // UTC
strncpy(config.device.tzdef, "UTC", sizeof(config.device.tzdef));
} else if (selected == 9) { // EU/Western
strncpy(config.device.tzdef, "GMT0BST,M3.5.0/1,M10.5.0", sizeof(config.device.tzdef));
} else if (selected == 11) { // EU/Central
} else if (selected == 10) { // EU/Central
strncpy(config.device.tzdef, "CET-1CEST,M3.5.0,M10.5.0/3", sizeof(config.device.tzdef));
} else if (selected == 12) { // EU/Eastern
} else if (selected == 11) { // EU/Eastern
strncpy(config.device.tzdef, "EET-2EEST,M3.5.0/3,M10.5.0/4", sizeof(config.device.tzdef));
} else if (selected == 13) { // Asia/Kolkata
} else if (selected == 12) { // Asia/Kolkata
strncpy(config.device.tzdef, "IST-5:30", sizeof(config.device.tzdef));
} else if (selected == 14) { // China
} else if (selected == 13) { // China
strncpy(config.device.tzdef, "HKT-8", sizeof(config.device.tzdef));
} else if (selected == 15) { // AU/AWST
} else if (selected == 14) { // AU/AWST
strncpy(config.device.tzdef, "AWST-8", sizeof(config.device.tzdef));
} else if (selected == 16) { // AU/ACST
} else if (selected == 15) { // AU/ACST
strncpy(config.device.tzdef, "ACST-9:30ACDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef));
} else if (selected == 17) { // AU/AEST
} else if (selected == 16) { // AU/AEST
strncpy(config.device.tzdef, "AEST-10AEDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef));
} else if (selected == 18) { // NZ
} else if (selected == 17) { // NZ
strncpy(config.device.tzdef, "NZST-12NZDT,M9.5.0,M4.1.0/3", sizeof(config.device.tzdef));
}
if (selected != 0) {
@@ -262,25 +236,27 @@ 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) {
optionsArray[options] = "Reply via Freetext";
optionsEnumArray[options++] = Freetext;
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;
}
#ifdef HAS_I2S
optionsArray[options] = "Read Aloud";
optionsEnumArray[options++] = Aloud;
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext", "Read Aloud"};
optionsArrayPtr = optionsArray;
options = 5;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsArrayPtr = optionsArrayPtr;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dismiss) {
@@ -299,7 +275,7 @@ void menuHandler::messageResponseMenu()
}
}
#ifdef HAS_I2S
else if (selected == Aloud) {
else if (selected == 4) {
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes);
@@ -312,10 +288,10 @@ void menuHandler::messageResponseMenu()
void menuHandler::homeBaseMenu()
{
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Sleep, enumEnd };
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
static const char *optionsArray[6] = {"Back"};
static int optionsEnumArray[6] = {Back};
int options = 1;
#ifdef PIN_EINK_EN
@@ -334,6 +310,8 @@ void menuHandler::homeBaseMenu()
optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext;
}
optionsArray[options] = "Bluetooth Toggle";
optionsEnumArray[options++] = Bluetooth;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Home Action";
@@ -358,35 +336,9 @@ void menuHandler::homeBaseMenu()
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else if (selected == Freetext) {
cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST);
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::textMessageBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "New Preset Msg";
optionsEnumArray[options++] = Preset;
if (kb_found) {
optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext;
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Preset) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} 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);
}
};
screen->showOverlayBanner(bannerOptions);
@@ -394,25 +346,37 @@ void menuHandler::textMessageBaseMenu()
void menuHandler::systemBaseMenu()
{
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Notifications;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \
defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Options";
optionsEnumArray[options++] = ScreenOptions;
// Check if brightness is supported
bool hasSupportBrightness = false;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || HAS_TFT
hasSupportBrightness = true;
#endif
optionsArray[options] = "Bluetooth Toggle";
optionsEnumArray[options++] = Bluetooth;
enum optionsNumbers { Back, Beeps, Brightness, Reboot, Color, MUI, Test };
static const char *optionsArray[7] = {"Back"};
static int optionsEnumArray[7] = {Back};
int options = 1;
optionsArray[options] = "Reboot/Shutdown";
optionsEnumArray[options++] = PowerMenu;
optionsArray[options] = "Beeps Action";
optionsEnumArray[options++] = Beeps;
if (hasSupportBrightness) {
optionsArray[options] = "Brightness";
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;
#endif
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsEnumArray[options++] = MUI;
#endif
if (test_enabled) {
optionsArray[options] = "Test Menu";
optionsEnumArray[options++] = Test;
@@ -424,21 +388,24 @@ void menuHandler::systemBaseMenu()
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Notifications) {
menuHandler::menuQueue = menuHandler::notifications_menu;
if (selected == Beeps) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
screen->runNow();
} else if (selected == ScreenOptions) {
menuHandler::menuQueue = menuHandler::screen_options_menu;
} else if (selected == Brightness) {
menuHandler::menuQueue = menuHandler::brightness_picker;
screen->runNow();
} else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::power_menu;
} else if (selected == Reboot) {
menuHandler::menuQueue = menuHandler::reboot_menu;
screen->runNow();
} else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow();
} else if (selected == Color) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow();
} else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu;
screen->runNow();
} else if (selected == Bluetooth) {
menuQueue = bluetooth_toggle_menu;
screen->runNow();
} else if (selected == Back && !test_enabled) {
test_count++;
if (test_count > 4) {
@@ -451,37 +418,30 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd };
static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2;
int options;
static const char **optionsArrayPtr;
if (kb_found) {
optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext;
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] = "Trace Route";
optionsEnumArray[options++] = TraceRoute;
optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Favorites Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsArrayPtr = optionsArrayPtr;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Preset) {
if (selected == 1) {
cannedMessageModule->LaunchWithDestination(graphics::UIRenderer::currentFavoriteNodeNum);
} else if (selected == Freetext) {
} else if (selected == 2 && kb_found) {
cannedMessageModule->LaunchFreetextWithDestination(graphics::UIRenderer::currentFavoriteNodeNum);
} else if (selected == Remove) {
} else if ((!kb_found && selected == 2) || (selected == 3 && kb_found)) {
menuHandler::menuQueue = menuHandler::remove_favorite;
screen->runNow();
} else if (selected == TraceRoute) {
if (traceRouteModule) {
traceRouteModule->launch(graphics::UIRenderer::currentFavoriteNodeNum);
}
}
};
screen->showOverlayBanner(bannerOptions);
@@ -489,29 +449,34 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu()
{
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;
int options;
static const char **optionsArrayPtr;
static const char *optionsArray[] = {"Back", "GPS Toggle", "Compass"};
static const char *optionsArrayCalibrate[] = {"Back", "GPS Toggle", "Compass", "Compass Calibrate"};
if (accelerometerThread) {
optionsArray[options] = "Compass Calibrate";
optionsEnumArray[options++] = CompassCalibrate;
optionsArrayPtr = optionsArrayCalibrate;
options = 4;
} else {
optionsArrayPtr = optionsArray;
options = 3;
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsArrayPtr = optionsArrayPtr;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == GPSToggle) {
if (selected == 1) {
#if MESHTASTIC_EXCLUDE_GPS
menuQueue = menu_none;
#else
menuQueue = gps_toggle_menu;
screen->runNow();
} else if (selected == CompassMenu) {
#endif
} else if (selected == 2) {
menuQueue = compass_point_north_menu;
screen->runNow();
} else if (selected == CompassCalibrate) {
} else if (selected == 3) {
accelerometerThread->calibrate(30);
}
};
@@ -520,25 +485,18 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu()
{
enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd };
static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"};
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset NodeDB"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 5;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Favorite) {
if (selected == 1) {
menuQueue = add_favorite;
screen->runNow();
} else if (selected == Verify) {
menuQueue = key_verification_init;
screen->runNow();
} else if (selected == Reset) {
} else if (selected == 2) {
menuQueue = reset_node_db_menu;
screen->runNow();
} else if (selected == TraceRoute) {
menuQueue = trace_route_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
@@ -564,7 +522,6 @@ 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?";
@@ -572,25 +529,28 @@ void menuHandler::compassNorthMenu()
bannerOptions.optionsCount = 4;
bannerOptions.InitialSelected = uiconfig.compass_mode + 1;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dynamic) {
if (selected == 1) {
if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) {
uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC;
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Fixed) {
} else if (selected == 2) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING;
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Freeze) {
} else if (selected == 3) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING;
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Back) {
} else if (selected == 0) {
menuQueue = position_base_menu;
screen->runNow();
}
@@ -601,7 +561,6 @@ void menuHandler::compassNorthMenu()
#if !MESHTASTIC_EXCLUDE_GPS
void menuHandler::GPSToggleMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle GPS";
@@ -628,28 +587,11 @@ 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"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Buzzer Mode";
bannerOptions.message = "Beep Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -699,7 +641,7 @@ void menuHandler::BrightnessPickerMenu()
#endif
// Save to device
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
LOG_INFO("Screen brightness set to %d", uiconfig.screen_brightness);
}
@@ -710,13 +652,13 @@ void menuHandler::BrightnessPickerMenu()
void menuHandler::switchToMUIMenu()
{
static const char *optionsArray[] = {"No", "Yes"};
static const char *optionsArray[] = {"Yes", "No"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Switch to MUI?";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
if (selected == 0) {
config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR;
config.bluetooth.enabled = false;
service->reloadConfig(SEGMENT_CONFIG);
@@ -735,71 +677,68 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 10;
bannerOptions.bannerCallback = [display](int selected) -> void {
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
uint8_t TFT_MESH_r = 0;
uint8_t TFT_MESH_g = 0;
uint8_t TFT_MESH_b = 0;
uint8_t r = 0;
uint8_t g = 0;
uint8_t 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");
TFT_MESH_r = 103;
TFT_MESH_g = 234;
TFT_MESH_b = 148;
r = 103;
g = 234;
b = 148;
} else if (selected == 3) {
LOG_INFO("Setting color to Yellow");
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 128;
r = 255;
g = 255;
b = 128;
} else if (selected == 4) {
LOG_INFO("Setting color to Red");
TFT_MESH_r = 255;
TFT_MESH_g = 64;
TFT_MESH_b = 64;
r = 255;
g = 64;
b = 64;
} else if (selected == 5) {
LOG_INFO("Setting color to Orange");
TFT_MESH_r = 255;
TFT_MESH_g = 160;
TFT_MESH_b = 20;
r = 255;
g = 160;
b = 20;
} else if (selected == 6) {
LOG_INFO("Setting color to Purple");
TFT_MESH_r = 204;
TFT_MESH_g = 153;
TFT_MESH_b = 255;
r = 204;
g = 153;
b = 255;
} else if (selected == 7) {
LOG_INFO("Setting color to Teal");
TFT_MESH_r = 64;
TFT_MESH_g = 224;
TFT_MESH_b = 208;
r = 64;
g = 224;
b = 208;
} else if (selected == 8) {
LOG_INFO("Setting color to Pink");
TFT_MESH_r = 255;
TFT_MESH_g = 105;
TFT_MESH_b = 180;
r = 255;
g = 105;
b = 180;
} else if (selected == 9) {
LOG_INFO("Setting color to White");
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
} else {
menuQueue = system_base_menu;
screen->runNow();
r = 255;
g = 255;
b = 255;
}
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
if (selected != 0) {
display->setColor(BLACK);
display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
display->setColor(WHITE);
if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
if (r == 0 && g == 0 && b == 0) {
#ifdef TFT_MESH_OVERRIDE
TFT_MESH = TFT_MESH_OVERRIDE;
#else
TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
#endif
} else {
TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
TFT_MESH = COLOR565(r, g, b);
}
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
@@ -807,13 +746,13 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
#endif
screen->setFrames(graphics::Screen::FOCUS_SYSTEM);
if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
if (r == 0 && g == 0 && b == 0) {
uiconfig.screen_rgb_color = 0;
} else {
uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b;
uiconfig.screen_rgb_color = (r << 16) | (g << 8) | b;
}
LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color);
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
}
#endif
};
@@ -832,28 +771,6 @@ void menuHandler::rebootMenu()
IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0));
nodeDB->saveToDisk();
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
} else {
menuQueue = power_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::shutdownMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Shutdown Device?";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else {
menuQueue = power_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
@@ -861,7 +778,7 @@ void menuHandler::shutdownMenu()
void menuHandler::addFavoriteMenu()
{
screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
screen->showNodePicker("Node To Favorite", 30000, [](int nodenum) -> void {
LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
@@ -883,24 +800,13 @@ void menuHandler::removeFavoriteMenu()
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
LOG_INFO("Removing %x as favorite node", graphics::UIRenderer::currentFavoriteNodeNum);
nodeDB->set_favorite(false, graphics::UIRenderer::currentFavoriteNodeNum);
screen->setFrames(graphics::Screen::FOCUS_DEFAULT);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::traceRouteMenu()
{
screen->showNodePicker("Node to Trace", 30000, [](uint32_t nodenum) -> void {
LOG_INFO("Menu: Node picker selected node 0x%08x, traceRouteModule=%p", nodenum, traceRouteModule);
if (traceRouteModule) {
traceRouteModule->startTraceRoute(nodenum);
}
});
}
void menuHandler::testMenu()
{
@@ -963,153 +869,6 @@ void menuHandler::wifiToggleMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::notificationsMenu()
{
enum optionsNumbers { Back, BuzzerActions };
static const char *optionsArray[] = {"Back", "Buzzer Actions"};
static int optionsEnumArray[] = {Back, BuzzerActions};
int options = 2;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Notifications";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == BuzzerActions) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::screenOptionsMenu()
{
// Check if brightness is supported
bool hasSupportBrightness = false;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
hasSupportBrightness = true;
#endif
#if defined(T_DECK)
// TDeck Doesn't seem to support brightness at all, at least not reliably
hasSupportBrightness = false;
#endif
enum optionsNumbers { Back, Brightness, ScreenColor };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
// Only show brightness for B&W displays
if (hasSupportBrightness) {
optionsArray[options] = "Brightness";
optionsEnumArray[options++] = Brightness;
}
// Only show screen color for TFT displays
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = ScreenColor;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Screen Options";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Brightness) {
menuHandler::menuQueue = menuHandler::brightness_picker;
screen->runNow();
} else if (selected == ScreenColor) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::powerMenu()
{
enum optionsNumbers { Back, Reboot, Shutdown, MUI };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
optionsArray[options] = "Reboot";
optionsEnumArray[options++] = Reboot;
optionsArray[options] = "Shutdown";
optionsEnumArray[options++] = Shutdown;
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsEnumArray[options++] = MUI;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Reboot / Shutdown";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Reboot) {
menuHandler::menuQueue = menuHandler::reboot_menu;
screen->runNow();
} else if (selected == Shutdown) {
menuHandler::menuQueue = menuHandler::shutdown_menu;
screen->runNow();
} else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
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)
@@ -1132,9 +891,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case clock_menu:
clockMenu();
break;
case system_base_menu:
systemBaseMenu();
break;
case position_base_menu:
positionBaseMenu();
break;
@@ -1164,18 +920,12 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case reboot_menu:
rebootMenu();
break;
case shutdown_menu:
shutdownMenu();
break;
case add_favorite:
addFavoriteMenu();
break;
case remove_favorite:
removeFavoriteMenu();
break;
case trace_route_menu:
traceRouteMenu();
break;
case test_menu:
testMenu();
break;
@@ -1185,36 +935,10 @@ 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 notifications_menu:
notificationsMenu();
break;
case screen_options_menu:
screenOptionsMenu();
break;
case power_menu:
powerMenu();
break;
case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break;
}
menuQueue = menu_none;
}
void menuHandler::saveUIConfig()
{
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
}
} // namespace graphics
#endif

View File

@@ -1,5 +1,3 @@
#pragma once
#if HAS_SCREEN
#include "configuration.h"
namespace graphics
{
@@ -23,34 +21,22 @@ class menuHandler
tftcolormenupicker,
brightness_picker,
reboot_menu,
shutdown_menu,
add_favorite,
remove_favorite,
test_menu,
number_test,
wifi_toggle_menu,
bluetooth_toggle_menu,
notifications_menu,
screen_options_menu,
power_menu,
system_base_menu,
key_verification_init,
key_verification_final_prompt,
trace_route_menu,
throttle_message,
wifi_toggle_menu
};
static screenMenus menuQueue;
static void LoraRegionPicker(uint32_t duration = 30000);
static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
static void clockMenu();
static void TZPicker();
static void TwelveHourPicker();
static void ClockFacePicker();
static void messageResponseMenu();
static void homeBaseMenu();
static void textMessageBaseMenu();
static void systemBaseMenu();
static void favoriteBaseMenu();
static void positionBaseMenu();
@@ -63,24 +49,12 @@ class menuHandler
static void resetNodeDBMenu();
static void BrightnessPickerMenu();
static void rebootMenu();
static void shutdownMenu();
static void addFavoriteMenu();
static void removeFavoriteMenu();
static void traceRouteMenu();
static void testMenu();
static void numberTest();
static void wifiBaseMenu();
static void wifiToggleMenu();
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
private:
static void saveUIConfig();
static void keyVerificationInitMenu();
static void keyVerificationFinalPrompt();
static void BluetoothToggleMenu();
};
} // namespace graphics
#endif

View File

@@ -273,7 +273,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
currentKey ^= ((size_t)mp.id << 24);
if (cachedKey != currentKey) {
LOG_INFO("Onscreen message scroll cache key needs updating: cachedKey=0x%0x, currentKey=0x%x", cachedKey, currentKey);
LOG_INFO("Message cache key is misssed cachedKey=0x%0x, currentKey=0x%x", cachedKey, currentKey);
// Cache miss - regenerate lines and heights
cachedLines = generateLines(display, headerStr, messageBuf, textWidth);

View File

@@ -26,7 +26,7 @@ extern bool hasUnreadMessage;
namespace graphics
{
InputEvent NotificationRenderer::inEvent;
char NotificationRenderer::inEvent = INPUT_BROKER_NONE;
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 (uint32_t i = 0; i < n; i++) {
for (int i = 0; i < n; i++) {
ret *= 10;
}
return ret;
@@ -72,31 +72,14 @@ 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);
@@ -129,40 +112,31 @@ 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.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent == INPUT_BROKER_UP || inEvent == 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.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == 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.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));
} else if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_RIGHT) {
curSelected++;
}
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_RIGHT) {
curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
} else if (inEvent == INPUT_BROKER_LEFT) {
curSelected--;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
resetBanner();
return;
}
if (curSelected == static_cast<int8_t>(numDigits)) {
alertBannerCallback(currentNumber);
if (curSelected == numDigits) {
resetBanner();
return;
alertBannerCallback(currentNumber);
}
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0')
return;
@@ -170,12 +144,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 (uint16_t i = 0; i < lineCount; i++) {
for (int i = 0; i < lineCount; i++) {
linePointers[i] = lineStarts[i];
}
std::string digits = " ";
std::string arrowPointer = " ";
for (uint16_t i = 0; i < numDigits; i++) {
for (int 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) {
@@ -216,18 +190,16 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
}
// Handle input
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) {
curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
} else if (inEvent == INPUT_BROKER_SELECT) {
resetBanner();
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)
@@ -235,7 +207,7 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
if (curSelected == alertBannerOptions)
curSelected = 0;
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0')
return;
@@ -333,11 +305,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
// Handle input
if (alertBannerOptions > 0) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) {
curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
} else if (inEvent == INPUT_BROKER_SELECT) {
if (optionsEnumPtr != nullptr) {
alertBannerCallback(optionsEnumPtr[curSelected]);
optionsEnumPtr = nullptr;
@@ -345,11 +317,8 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
alertBannerCallback(curSelected);
}
resetBanner();
return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
resetBanner();
return;
}
if (curSelected == -1)
@@ -357,14 +326,12 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
if (curSelected == alertBannerOptions)
curSelected = 0;
} else {
if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_ALT_LONG ||
inEvent.inputEvent == INPUT_BROKER_CANCEL) {
if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_ALT_LONG || inEvent == INPUT_BROKER_CANCEL) {
resetBanner();
return;
}
}
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0')
return;

View File

@@ -11,8 +11,7 @@ namespace graphics
class NotificationRenderer
{
public:
static InputEvent inEvent;
static char inKeypress;
static char inEvent;
static int8_t curSelected;
static char alertBannerMessage[256];
static uint32_t alertBannerUntil; // 0 is a special case meaning forever

View File

@@ -24,23 +24,6 @@ extern graphics::Screen *screen;
namespace graphics
{
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
std::vector<meshtastic_NodeInfoLite *> graphics::UIRenderer::favoritedNodes;
void graphics::UIRenderer::rebuildFavoritedNodes()
{
favoritedNodes.clear();
size_t total = nodeDB->getNumMeshNodes();
for (size_t i = 0; i < total; i++) {
meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (!n || n->num == nodeDB->getNodeNum())
continue;
if (n->is_favorite)
favoritedNodes.push_back(n);
}
std::sort(favoritedNodes.begin(), favoritedNodes.end(),
[](const meshtastic_NodeInfoLite *a, const meshtastic_NodeInfoLite *b) { return a->num < b->num; });
}
#if !MESHTASTIC_EXCLUDE_GPS
// GeoCoord object for coordinate conversions
@@ -218,7 +201,27 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
// **********************
void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// --- Cache favorite nodes for the current frame only, to save computation ---
static std::vector<meshtastic_NodeInfoLite *> favoritedNodes;
static int prevFrame = -1;
// --- Only rebuild favorites list if we're on a new frame ---
if (state->currentFrame != prevFrame) {
prevFrame = state->currentFrame;
favoritedNodes.clear();
size_t total = nodeDB->getNumMeshNodes();
for (size_t i = 0; i < total; i++) {
meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
// Skip nulls and ourself
if (!n || n->num == nodeDB->getNodeNum())
continue;
if (n->is_favorite)
favoritedNodes.push_back(n);
}
// Keep a stable, consistent display order
std::sort(favoritedNodes.begin(), favoritedNodes.end(),
[](const meshtastic_NodeInfoLite *a, const meshtastic_NodeInfoLite *b) { return a->num < b->num; });
}
if (favoritedNodes.empty())
return;
@@ -654,7 +657,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
char combinedName[50];
snprintf(combinedName, sizeof(combinedName), "%s (%s)", longNameStr.empty() ? "" : longNameStr.c_str(), shortnameble);
if (SCREEN_WIDTH - (display->getStringWidth(combinedName)) > 10) {
if (SCREEN_WIDTH - (display->getStringWidth(longName) + display->getStringWidth(shortnameble)) > 10) {
size_t len = strlen(combinedName);
if (len >= 3 && strcmp(combinedName + len - 3, " ()") == 0) {
combinedName[len - 3] = '\0'; // Remove the last three characters
@@ -665,7 +668,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
nameX, ((rows == 4) ? getTextPositions(display)[line++] : getTextPositions(display)[line++]) + yOffset, combinedName);
} else {
// === LongName Centered ===
textWidth = display->getStringWidth(longNameStr.c_str());
textWidth = display->getStringWidth(longName);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], longNameStr.c_str());

View File

@@ -61,8 +61,6 @@ class UIRenderer
static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static NodeNum currentFavoriteNodeNum;
static std::vector<meshtastic_NodeInfoLite *> favoritedNodes;
static void rebuildFavoritedNodes();
// OEM screens
#ifdef USERPREFS_OEM_TEXT

View File

@@ -1,84 +0,0 @@
#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

View File

@@ -1,41 +0,0 @@
/*
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

View File

@@ -1,41 +0,0 @@
#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

View File

@@ -1,31 +0,0 @@
/*
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

View File

@@ -223,7 +223,7 @@ void InkHUD::MenuApplet::execute(MenuItem item)
case SHUTDOWN:
LOG_INFO("Shutting down from menu");
shutdownAtMsec = millis();
power->shutdown();
// Menu is then sent to background via onShutdown
break;

View File

@@ -39,8 +39,8 @@ void InkHUD::Events::begin()
void InkHUD::Events::onButtonShort()
{
// Audio feedback (via buzzer)
// Short tone
playChirp();
// Short low tone
playBoop();
// Cancel any beeping, buzzing, blinking
// Some button handling suppressed if we are dismissing an external notification (see below)
bool dismissedExt = dismissExternalNotification();
@@ -64,8 +64,8 @@ void InkHUD::Events::onButtonShort()
void InkHUD::Events::onButtonLong()
{
// Audio feedback (via buzzer)
// Slightly longer than playChirp
playBoop();
// Low tone, longer than playBoop
playBeep();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;

View File

@@ -1,6 +1,7 @@
[inkhud]
build_src_filter =
+<graphics/niche/>; Include the nicheGraphics directory
+<../variants/$PIOENV>; Include nicheGraphics.h from our variant folder
build_flags =
-D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics
-D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD (a NicheGraphics UI)

View File

@@ -53,6 +53,7 @@ bool ButtonThread::initButton(const ButtonConfig &config)
},
this);
if (config.longPress != INPUT_BROKER_NONE) {
_longPress = config.longPress;
userButton.attachLongPressStart(
[](void *callerThread) -> void {
@@ -68,6 +69,7 @@ bool ButtonThread::initButton(const ButtonConfig &config)
thread->btnEvent = BUTTON_EVENT_LONG_RELEASED;
},
this);
}
if (config.doublePress != INPUT_BROKER_NONE) {
_doublePress = config.doublePress;
@@ -200,11 +202,11 @@ int32_t ButtonThread::runOnce()
break;
}
if (_longPress != INPUT_BROKER_NONE) {
// Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event)
evt.inputEvent = _longPress;
this->notifyObservers(&evt);
}
// Reset combination tracking
waitingForLongPress = false;
@@ -251,7 +253,7 @@ int32_t ButtonThread::runOnce()
// may wake the board immediatedly.
case BUTTON_EVENT_LONG_RELEASED: {
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
LOG_INFO("LONG PRESS RELEASE");
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
(millis() - buttonPressStartTime) >= _longLongPressTime) {
evt.inputEvent = _longLongPress;

View File

@@ -18,13 +18,13 @@ struct ButtonConfig {
uint16_t longPressTime = 500;
input_broker_event doublePress = INPUT_BROKER_NONE;
input_broker_event longLongPress = INPUT_BROKER_NONE;
uint16_t longLongPressTime = 3900;
uint16_t longLongPressTime = 5000;
input_broker_event triplePress = INPUT_BROKER_NONE;
input_broker_event shortLong = INPUT_BROKER_NONE;
bool touchQuirk = false;
// Constructor to set required parameter
explicit ButtonConfig(uint8_t pin = 0) : pinNumber(pin) {}
ButtonConfig(uint8_t pin = 0) : pinNumber(pin) {}
};
#ifndef BUTTON_CLICK_MS
@@ -62,7 +62,7 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
BUTTON_EVENT_COMBO_SHORT_LONG,
};
explicit ButtonThread(const char *name);
ButtonThread(const char *name);
int32_t runOnce() override;
OneButton userButton;
void attachButtonInterrupts();

View File

@@ -199,7 +199,7 @@ void ExpressLRSFiveWay::sendKey(input_broker_event key)
void ExpressLRSFiveWay::toggleGPS()
{
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
if (gps != nullptr) {
if (!config.device.disable_triple_click && (gps != nullptr)) {
gps->toggleGpsMode();
screen->startAlert("GPS Toggled");
alerting = true;
@@ -233,7 +233,14 @@ void ExpressLRSFiveWay::sendAdhocPing()
// Contained as one method for easier remapping of buttons by user
void ExpressLRSFiveWay::shutdown()
{
sendKey(INPUT_BROKER_SHUTDOWN);
LOG_INFO("Shutdown from long press");
powerFSM.trigger(EVENT_PRESS);
screen->startAlert("Shutting Down...");
// Don't set alerting = true. We don't want to auto-dismiss this alert.
playShutdownMelody(); // In case user adds a buzzer
shutdownAtMsec = millis() + 3000;
}
void ExpressLRSFiveWay::click()

View File

@@ -1,83 +0,0 @@
#ifdef ARCH_PORTDUINO
#include "SeesawRotary.h"
#include "input/InputBroker.h"
using namespace concurrency;
SeesawRotary *seesawRotary;
SeesawRotary::SeesawRotary(const char *name) : OSThread(name)
{
_originName = name;
}
bool SeesawRotary::init()
{
if (inputBroker)
inputBroker->registerSource(this);
if (!ss.begin(SEESAW_ADDR)) {
return false;
}
// attachButtonInterrupts();
uint32_t version = ((ss.getVersion() >> 16) & 0xFFFF);
if (version != 4991) {
LOG_WARN("Wrong firmware loaded? %u", version);
} else {
LOG_INFO("Found Product 4991");
}
/*
#ifdef ARCH_ESP32
// Register callbacks for before and after lightsleep
// Used to detach and reattach interrupts
lsObserver.observe(&notifyLightSleep);
lsEndObserver.observe(&notifyLightSleepEnd);
#endif
*/
ss.pinMode(SS_SWITCH, INPUT_PULLUP);
// get starting position
encoder_position = ss.getEncoderPosition();
ss.setGPIOInterrupts((uint32_t)1 << SS_SWITCH, 1);
ss.enableEncoderInterrupt();
canSleep = true; // Assume we should not keep the board awake
return true;
}
int32_t SeesawRotary::runOnce()
{
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
bool currentlyPressed = !ss.digitalRead(SS_SWITCH);
if (currentlyPressed && !wasPressed) {
e.inputEvent = INPUT_BROKER_SELECT;
}
wasPressed = currentlyPressed;
int32_t new_position = ss.getEncoderPosition();
// did we move arounde?
if (encoder_position != new_position) {
if (encoder_position == 0 && new_position != 1) {
e.inputEvent = INPUT_BROKER_ALT_PRESS;
} else if (new_position == 0 && encoder_position != 1) {
e.inputEvent = INPUT_BROKER_USER_PRESS;
} else if (new_position > encoder_position) {
e.inputEvent = INPUT_BROKER_USER_PRESS;
} else {
e.inputEvent = INPUT_BROKER_ALT_PRESS;
}
encoder_position = new_position;
}
if (e.inputEvent != INPUT_BROKER_NONE) {
e.source = this->_originName;
e.kbchar = 0x00;
this->notifyObservers(&e);
}
return 50;
}
#endif

View File

@@ -1,29 +0,0 @@
#pragma once
#ifdef ARCH_PORTDUINO
#include "Adafruit_seesaw.h"
#include "InputBroker.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#define SS_SWITCH 24
#define SS_NEOPIX 6
#define SEESAW_ADDR 0x36
class SeesawRotary : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
const char *_originName;
bool init();
SeesawRotary(const char *name);
int32_t runOnce() override;
private:
Adafruit_seesaw ss;
int32_t encoder_position;
bool wasPressed = false;
};
extern SeesawRotary *seesawRotary;
#endif

View File

@@ -1,18 +1,116 @@
// Based on the MPR121 Keyboard and Adafruit TCA8418 library
#include "TCA8418Keyboard.h"
#include "configuration.h"
#include <Arduino.h>
// REGISTERS
// #define _TCA8418_REG_RESERVED 0x00
#define _TCA8418_REG_CFG 0x01 // Configuration register
#define _TCA8418_REG_INT_STAT 0x02 // Interrupt status
#define _TCA8418_REG_KEY_LCK_EC 0x03 // Key lock and event counter
#define _TCA8418_REG_KEY_EVENT_A 0x04 // Key event register A
#define _TCA8418_REG_KEY_EVENT_B 0x05 // Key event register B
#define _TCA8418_REG_KEY_EVENT_C 0x06 // Key event register C
#define _TCA8418_REG_KEY_EVENT_D 0x07 // Key event register D
#define _TCA8418_REG_KEY_EVENT_E 0x08 // Key event register E
#define _TCA8418_REG_KEY_EVENT_F 0x09 // Key event register F
#define _TCA8418_REG_KEY_EVENT_G 0x0A // Key event register G
#define _TCA8418_REG_KEY_EVENT_H 0x0B // Key event register H
#define _TCA8418_REG_KEY_EVENT_I 0x0C // Key event register I
#define _TCA8418_REG_KEY_EVENT_J 0x0D // Key event register J
#define _TCA8418_REG_KP_LCK_TIMER 0x0E // Keypad lock1 to lock2 timer
#define _TCA8418_REG_UNLOCK_1 0x0F // Unlock register 1
#define _TCA8418_REG_UNLOCK_2 0x10 // Unlock register 2
#define _TCA8418_REG_GPIO_INT_STAT_1 0x11 // GPIO interrupt status 1
#define _TCA8418_REG_GPIO_INT_STAT_2 0x12 // GPIO interrupt status 2
#define _TCA8418_REG_GPIO_INT_STAT_3 0x13 // GPIO interrupt status 3
#define _TCA8418_REG_GPIO_DAT_STAT_1 0x14 // GPIO data status 1
#define _TCA8418_REG_GPIO_DAT_STAT_2 0x15 // GPIO data status 2
#define _TCA8418_REG_GPIO_DAT_STAT_3 0x16 // GPIO data status 3
#define _TCA8418_REG_GPIO_DAT_OUT_1 0x17 // GPIO data out 1
#define _TCA8418_REG_GPIO_DAT_OUT_2 0x18 // GPIO data out 2
#define _TCA8418_REG_GPIO_DAT_OUT_3 0x19 // GPIO data out 3
#define _TCA8418_REG_GPIO_INT_EN_1 0x1A // GPIO interrupt enable 1
#define _TCA8418_REG_GPIO_INT_EN_2 0x1B // GPIO interrupt enable 2
#define _TCA8418_REG_GPIO_INT_EN_3 0x1C // GPIO interrupt enable 3
#define _TCA8418_REG_KP_GPIO_1 0x1D // Keypad/GPIO select 1
#define _TCA8418_REG_KP_GPIO_2 0x1E // Keypad/GPIO select 2
#define _TCA8418_REG_KP_GPIO_3 0x1F // Keypad/GPIO select 3
#define _TCA8418_REG_GPI_EM_1 0x20 // GPI event mode 1
#define _TCA8418_REG_GPI_EM_2 0x21 // GPI event mode 2
#define _TCA8418_REG_GPI_EM_3 0x22 // GPI event mode 3
#define _TCA8418_REG_GPIO_DIR_1 0x23 // GPIO data direction 1
#define _TCA8418_REG_GPIO_DIR_2 0x24 // GPIO data direction 2
#define _TCA8418_REG_GPIO_DIR_3 0x25 // GPIO data direction 3
#define _TCA8418_REG_GPIO_INT_LVL_1 0x26 // GPIO edge/level detect 1
#define _TCA8418_REG_GPIO_INT_LVL_2 0x27 // GPIO edge/level detect 2
#define _TCA8418_REG_GPIO_INT_LVL_3 0x28 // GPIO edge/level detect 3
#define _TCA8418_REG_DEBOUNCE_DIS_1 0x29 // Debounce disable 1
#define _TCA8418_REG_DEBOUNCE_DIS_2 0x2A // Debounce disable 2
#define _TCA8418_REG_DEBOUNCE_DIS_3 0x2B // Debounce disable 3
#define _TCA8418_REG_GPIO_PULL_1 0x2C // GPIO pull-up disable 1
#define _TCA8418_REG_GPIO_PULL_2 0x2D // GPIO pull-up disable 2
#define _TCA8418_REG_GPIO_PULL_3 0x2E // GPIO pull-up disable 3
// #define _TCA8418_REG_RESERVED 0x2F
// FIELDS CONFIG REGISTER 1
#define _TCA8418_REG_CFG_AI 0x80 // Auto-increment for read/write
#define _TCA8418_REG_CFG_GPI_E_CGF 0x40 // Event mode config
#define _TCA8418_REG_CFG_OVR_FLOW_M 0x20 // Overflow mode enable
#define _TCA8418_REG_CFG_INT_CFG 0x10 // Interrupt config
#define _TCA8418_REG_CFG_OVR_FLOW_IEN 0x08 // Overflow interrupt enable
#define _TCA8418_REG_CFG_K_LCK_IEN 0x04 // Keypad lock interrupt enable
#define _TCA8418_REG_CFG_GPI_IEN 0x02 // GPI interrupt enable
#define _TCA8418_REG_CFG_KE_IEN 0x01 // Key events interrupt enable
// FIELDS INT_STAT REGISTER 2
#define _TCA8418_REG_STAT_CAD_INT 0x10 // Ctrl-alt-del seq status
#define _TCA8418_REG_STAT_OVR_FLOW_INT 0x08 // Overflow interrupt status
#define _TCA8418_REG_STAT_K_LCK_INT 0x04 // Key lock interrupt status
#define _TCA8418_REG_STAT_GPI_INT 0x02 // GPI interrupt status
#define _TCA8418_REG_STAT_K_INT 0x01 // Key events interrupt status
// FIELDS KEY_LCK_EC REGISTER 3
#define _TCA8418_REG_LCK_EC_K_LCK_EN 0x40 // Key lock enable
#define _TCA8418_REG_LCK_EC_LCK_2 0x20 // Keypad lock status 2
#define _TCA8418_REG_LCK_EC_LCK_1 0x10 // Keypad lock status 1
#define _TCA8418_REG_LCK_EC_KLEC_3 0x08 // Key event count bit 3
#define _TCA8418_REG_LCK_EC_KLEC_2 0x04 // Key event count bit 2
#define _TCA8418_REG_LCK_EC_KLEC_1 0x02 // Key event count bit 1
#define _TCA8418_REG_LCK_EC_KLEC_0 0x01 // Key event count bit 0
// Pin IDs for matrix rows/columns
enum {
_TCA8418_ROW0, // Pin ID for row 0
_TCA8418_ROW1, // Pin ID for row 1
_TCA8418_ROW2, // Pin ID for row 2
_TCA8418_ROW3, // Pin ID for row 3
_TCA8418_ROW4, // Pin ID for row 4
_TCA8418_ROW5, // Pin ID for row 5
_TCA8418_ROW6, // Pin ID for row 6
_TCA8418_ROW7, // Pin ID for row 7
_TCA8418_COL0, // Pin ID for column 0
_TCA8418_COL1, // Pin ID for column 1
_TCA8418_COL2, // Pin ID for column 2
_TCA8418_COL3, // Pin ID for column 3
_TCA8418_COL4, // Pin ID for column 4
_TCA8418_COL5, // Pin ID for column 5
_TCA8418_COL6, // Pin ID for column 6
_TCA8418_COL7, // Pin ID for column 7
_TCA8418_COL8, // Pin ID for column 8
_TCA8418_COL9 // Pin ID for column 9
};
#define _TCA8418_COLS 3
#define _TCA8418_ROWS 4
#define _TCA8418_NUM_KEYS 12
#define _TCA8418_LONG_PRESS_THRESHOLD 2000
#define _TCA8418_MULTI_TAP_THRESHOLD 750
uint8_t TCA8418TapMod[_TCA8418_NUM_KEYS] = {13, 7, 7, 7, 7, 7,
9, 7, 9, 2, 2, 2}; // Num chars per key, Modulus for rotating through characters
using Key = TCA8418KeyboardBase::TCA8418Key;
// Num chars per key, Modulus for rotating through characters
static uint8_t TCA8418TapMod[_TCA8418_NUM_KEYS] = {13, 7, 7, 7, 7, 7, 9, 7, 9, 2, 2, 2};
static unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = {
unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = {
{'1', '.', ',', '?', '!', ':', ';', '-', '_', '\\', '/', '(', ')'}, // 1
{'2', 'a', 'b', 'c', 'A', 'B', 'C'}, // 2
{'3', 'd', 'e', 'f', 'D', 'E', 'F'}, // 3
@@ -27,35 +125,176 @@ static unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = {
{'#', '@'}, // #
};
static unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = {
Key::ESC, // 1
Key::UP, // 2
Key::NONE, // 3
Key::LEFT, // 4
Key::NONE, // 5
Key::RIGHT, // 6
Key::NONE, // 7
Key::DOWN, // 8
Key::NONE, // 9
Key::BSP, // *
Key::NONE, // 0
Key::NONE, // #
unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = {
_TCA8418_ESC, // 1
_TCA8418_UP, // 2
_TCA8418_NONE, // 3
_TCA8418_LEFT, // 4
_TCA8418_NONE, // 5
_TCA8418_RIGHT, // 6
_TCA8418_NONE, // 7
_TCA8418_DOWN, // 8
_TCA8418_NONE, // 9
_TCA8418_BSP, // *
_TCA8418_NONE, // 0
_TCA8418_NONE, // #
};
TCA8418Keyboard::TCA8418Keyboard()
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(-1), next_key(-1), last_tap(0L), char_idx(0), tap_interval(0),
should_backspace(false)
#define _TCA8418_LONG_PRESS_THRESHOLD 2000
#define _TCA8418_MULTI_TAP_THRESHOLD 750
TCA8418Keyboard::TCA8418Keyboard() : m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr)
{
state = Init;
last_key = -1;
should_backspace = false;
last_tap = 0L;
char_idx = 0;
tap_interval = 0;
backlight_on = true;
queue = "";
}
void TCA8418Keyboard::begin(uint8_t addr, TwoWire *wire)
{
m_addr = addr;
m_wire = wire;
m_wire->begin();
reset();
}
void TCA8418Keyboard::begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr)
{
m_addr = addr;
m_wire = nullptr;
writeCallback = w;
readCallback = r;
reset();
}
void TCA8418Keyboard::reset()
{
TCA8418KeyboardBase::reset();
LOG_DEBUG("TCA8418 Reset");
// GPIO
// set default all GIO pins to INPUT
writeRegister(_TCA8418_REG_GPIO_DIR_1, 0x00);
writeRegister(_TCA8418_REG_GPIO_DIR_2, 0x00);
// Set COL9 as GPIO output
writeRegister(TCA8418_REG_GPIO_DIR_3, 0x02);
writeRegister(_TCA8418_REG_GPIO_DIR_3, 0x02);
// Switch off keyboard backlight (COL9 = LOW)
writeRegister(TCA8418_REG_GPIO_DAT_OUT_3, 0x00);
writeRegister(_TCA8418_REG_GPIO_DAT_OUT_3, 0x00);
// add all pins to key events
writeRegister(_TCA8418_REG_GPI_EM_1, 0xFF);
writeRegister(_TCA8418_REG_GPI_EM_2, 0xFF);
writeRegister(_TCA8418_REG_GPI_EM_3, 0xFF);
// set all pins to FALLING interrupts
writeRegister(_TCA8418_REG_GPIO_INT_LVL_1, 0x00);
writeRegister(_TCA8418_REG_GPIO_INT_LVL_2, 0x00);
writeRegister(_TCA8418_REG_GPIO_INT_LVL_3, 0x00);
// add all pins to interrupts
writeRegister(_TCA8418_REG_GPIO_INT_EN_1, 0xFF);
writeRegister(_TCA8418_REG_GPIO_INT_EN_2, 0xFF);
writeRegister(_TCA8418_REG_GPIO_INT_EN_3, 0xFF);
// Set keyboard matrix size
matrix(_TCA8418_ROWS, _TCA8418_COLS);
enableDebounce();
flush();
state = Idle;
}
bool TCA8418Keyboard::matrix(uint8_t rows, uint8_t columns)
{
if ((rows > 8) || (columns > 10))
return false;
// Skip zero size matrix
if ((rows != 0) && (columns != 0)) {
// Setup the keypad matrix.
uint8_t mask = 0x00;
for (int r = 0; r < rows; r++) {
mask <<= 1;
mask |= 1;
}
writeRegister(_TCA8418_REG_KP_GPIO_1, mask);
mask = 0x00;
for (int c = 0; c < columns && c < 8; c++) {
mask <<= 1;
mask |= 1;
}
writeRegister(_TCA8418_REG_KP_GPIO_2, mask);
if (columns > 8) {
if (columns == 9)
mask = 0x01;
else
mask = 0x03;
writeRegister(_TCA8418_REG_KP_GPIO_3, mask);
}
}
return true;
}
uint8_t TCA8418Keyboard::keyCount() const
{
uint8_t eventCount = readRegister(_TCA8418_REG_KEY_LCK_EC);
eventCount &= 0x0F; // lower 4 bits only
return eventCount;
}
bool TCA8418Keyboard::hasEvent()
{
return queue.length() > 0;
}
void TCA8418Keyboard::queueEvent(char next)
{
if (next == _TCA8418_NONE) {
return;
}
queue.concat(next);
}
char TCA8418Keyboard::dequeueEvent()
{
if (queue.length() < 1) {
return _TCA8418_NONE;
}
char next = queue.charAt(0);
queue.remove(0, 1);
return next;
}
void TCA8418Keyboard::trigger()
{
if (keyCount() == 0) {
return;
}
if (state != Init) {
// Read the key register
uint8_t k = readRegister(_TCA8418_REG_KEY_EVENT_A);
uint8_t key = k & 0x7F;
if (k & 0x80) {
if (state == Idle)
pressed(key);
return;
} else {
if (state == Held) {
released();
}
state = Idle;
return;
}
} else {
reset();
}
}
void TCA8418Keyboard::pressed(uint8_t key)
@@ -115,7 +354,7 @@ void TCA8418Keyboard::released()
int32_t held_interval = now - last_tap;
last_tap = now;
if (tap_interval < _TCA8418_MULTI_TAP_THRESHOLD && should_backspace) {
queueEvent(BSP);
queueEvent(_TCA8418_BSP);
}
if (held_interval > _TCA8418_LONG_PRESS_THRESHOLD) {
queueEvent(TCA8418LongPressMap[last_key]);
@@ -127,11 +366,195 @@ void TCA8418Keyboard::released()
}
}
uint8_t TCA8418Keyboard::flush()
{
// Flush key events
uint8_t count = 0;
while (readRegister(_TCA8418_REG_KEY_EVENT_A) != 0)
count++;
// Flush gpio events
readRegister(_TCA8418_REG_GPIO_INT_STAT_1);
readRegister(_TCA8418_REG_GPIO_INT_STAT_2);
readRegister(_TCA8418_REG_GPIO_INT_STAT_3);
// Clear INT_STAT register
writeRegister(_TCA8418_REG_INT_STAT, 3);
return count;
}
uint8_t TCA8418Keyboard::digitalRead(uint8_t pinnum) const
{
if (pinnum > _TCA8418_COL9)
return 0xFF;
uint8_t reg = _TCA8418_REG_GPIO_DAT_STAT_1 + pinnum / 8;
uint8_t mask = (1 << (pinnum % 8));
// Level 0 = low other = high
uint8_t value = readRegister(reg);
if (value & mask)
return HIGH;
return LOW;
}
bool TCA8418Keyboard::digitalWrite(uint8_t pinnum, uint8_t level)
{
if (pinnum > _TCA8418_COL9)
return false;
uint8_t reg = _TCA8418_REG_GPIO_DAT_OUT_1 + pinnum / 8;
uint8_t mask = (1 << (pinnum % 8));
// Level 0 = low other = high
uint8_t value = readRegister(reg);
if (level == LOW)
value &= ~mask;
else
value |= mask;
writeRegister(reg, value);
return true;
}
bool TCA8418Keyboard::pinMode(uint8_t pinnum, uint8_t mode)
{
if (pinnum > _TCA8418_COL9)
return false;
uint8_t idx = pinnum / 8;
uint8_t reg = _TCA8418_REG_GPIO_DIR_1 + idx;
uint8_t mask = (1 << (pinnum % 8));
// Mode 0 = input 1 = output
uint8_t value = readRegister(reg);
if (mode == OUTPUT)
value |= mask;
else
value &= ~mask;
writeRegister(reg, value);
// Pullup 0 = enabled 1 = disabled
reg = _TCA8418_REG_GPIO_PULL_1 + idx;
value = readRegister(reg);
if (mode == INPUT_PULLUP)
value &= ~mask;
else
value |= mask;
writeRegister(reg, value);
return true;
}
bool TCA8418Keyboard::pinIRQMode(uint8_t pinnum, uint8_t mode)
{
if (pinnum > _TCA8418_COL9)
return false;
if ((mode != RISING) && (mode != FALLING))
return false;
// Mode 0 = falling 1 = rising
uint8_t idx = pinnum / 8;
uint8_t reg = _TCA8418_REG_GPIO_INT_LVL_1 + idx;
uint8_t mask = (1 << (pinnum % 8));
uint8_t value = readRegister(reg);
if (mode == RISING)
value |= mask;
else
value &= ~mask;
writeRegister(reg, value);
// Enable interrupt
reg = _TCA8418_REG_GPIO_INT_EN_1 + idx;
value = readRegister(reg);
value |= mask;
writeRegister(reg, value);
return true;
}
void TCA8418Keyboard::enableInterrupts()
{
uint8_t value = readRegister(_TCA8418_REG_CFG);
value |= (_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN);
writeRegister(_TCA8418_REG_CFG, value);
};
void TCA8418Keyboard::disableInterrupts()
{
uint8_t value = readRegister(_TCA8418_REG_CFG);
value &= ~(_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN);
writeRegister(_TCA8418_REG_CFG, value);
};
void TCA8418Keyboard::enableMatrixOverflow()
{
uint8_t value = readRegister(_TCA8418_REG_CFG);
value |= _TCA8418_REG_CFG_OVR_FLOW_M;
writeRegister(_TCA8418_REG_CFG, value);
};
void TCA8418Keyboard::disableMatrixOverflow()
{
uint8_t value = readRegister(_TCA8418_REG_CFG);
value &= ~_TCA8418_REG_CFG_OVR_FLOW_M;
writeRegister(_TCA8418_REG_CFG, value);
};
void TCA8418Keyboard::enableDebounce()
{
writeRegister(_TCA8418_REG_DEBOUNCE_DIS_1, 0x00);
writeRegister(_TCA8418_REG_DEBOUNCE_DIS_2, 0x00);
writeRegister(_TCA8418_REG_DEBOUNCE_DIS_3, 0x00);
}
void TCA8418Keyboard::disableDebounce()
{
writeRegister(_TCA8418_REG_DEBOUNCE_DIS_1, 0xFF);
writeRegister(_TCA8418_REG_DEBOUNCE_DIS_2, 0xFF);
writeRegister(_TCA8418_REG_DEBOUNCE_DIS_3, 0xFF);
}
void TCA8418Keyboard::setBacklight(bool on)
{
if (on) {
digitalWrite(TCA8418_COL9, HIGH);
digitalWrite(_TCA8418_COL9, HIGH);
} else {
digitalWrite(TCA8418_COL9, LOW);
digitalWrite(_TCA8418_COL9, LOW);
}
}
uint8_t TCA8418Keyboard::readRegister(uint8_t reg) const
{
if (m_wire) {
m_wire->beginTransmission(m_addr);
m_wire->write(reg);
m_wire->endTransmission();
m_wire->requestFrom(m_addr, (uint8_t)1);
if (m_wire->available() < 1)
return 0;
return m_wire->read();
}
if (readCallback) {
uint8_t data;
readCallback(m_addr, reg, &data, 1);
return data;
}
return 0;
}
void TCA8418Keyboard::writeRegister(uint8_t reg, uint8_t value)
{
uint8_t data[2];
data[0] = reg;
data[1] = value;
if (m_wire) {
m_wire->beginTransmission(m_addr);
m_wire->write(data, sizeof(uint8_t) * 2);
m_wire->endTransmission();
}
if (writeCallback) {
writeCallback(m_addr, data[0], &(data[1]), 1);
}
}

View File

@@ -1,23 +1,82 @@
#include "TCA8418KeyboardBase.h"
// Based on the MPR121 Keyboard and Adafruit TCA8418 library
#include "configuration.h"
#include <Wire.h>
/**
* @brief 3x4 keypad with 3 columns and 4 rows
*/
class TCA8418Keyboard : public TCA8418KeyboardBase
#define _TCA8418_NONE 0x00
#define _TCA8418_REBOOT 0x90
#define _TCA8418_LEFT 0xb4
#define _TCA8418_UP 0xb5
#define _TCA8418_DOWN 0xb6
#define _TCA8418_RIGHT 0xb7
#define _TCA8418_ESC 0x1b
#define _TCA8418_BSP 0x08
#define _TCA8418_SELECT 0x0d
class TCA8418Keyboard
{
public:
TCA8418Keyboard();
void reset(void) override;
void setBacklight(bool on) override;
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
protected:
void pressed(uint8_t key) override;
void released(void) override;
enum KeyState { Init = 0, Idle, Held, Busy };
KeyState state;
int8_t last_key;
int8_t next_key;
bool should_backspace;
uint32_t last_tap;
uint8_t char_idx;
int32_t tap_interval;
bool should_backspace;
bool backlight_on;
String queue;
TCA8418Keyboard();
void begin(uint8_t addr = XPOWERS_AXP192_AXP2101_ADDRESS, TwoWire *wire = &Wire);
void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = XPOWERS_AXP192_AXP2101_ADDRESS);
void reset(void);
// Configure the size of the keypad.
// All other rows and columns are set as inputs.
bool matrix(uint8_t rows, uint8_t columns);
// Flush all events in the FIFO buffer + GPIO events.
uint8_t flush(void);
// Key events available in the internal FIFO buffer.
uint8_t keyCount(void) const;
void trigger(void);
void pressed(uint8_t key);
void released(void);
bool hasEvent(void);
char dequeueEvent(void);
void queueEvent(char);
uint8_t digitalRead(uint8_t pinnum) const;
bool digitalWrite(uint8_t pinnum, uint8_t level);
bool pinMode(uint8_t pinnum, uint8_t mode);
bool pinIRQMode(uint8_t pinnum, uint8_t mode); // MODE FALLING or RISING
// enable / disable interrupts for matrix and GPI pins
void enableInterrupts();
void disableInterrupts();
// ignore key events when FIFO buffer is full or not.
void enableMatrixOverflow();
void disableMatrixOverflow();
// debounce keys.
void enableDebounce();
void disableDebounce();
void setBacklight(bool on);
uint8_t readRegister(uint8_t reg) const;
void writeRegister(uint8_t reg, uint8_t value);
private:
TwoWire *m_wire;
uint8_t m_addr;
i2c_com_fptr_t readCallback;
i2c_com_fptr_t writeCallback;
};

View File

@@ -1,372 +0,0 @@
// Based on the MPR121 Keyboard and Adafruit TCA8418 library
#include "TCA8418KeyboardBase.h"
#include "configuration.h"
#include <Arduino.h>
// FIELDS CONFIG REGISTER 1
#define _TCA8418_REG_CFG_AI 0x80 // Auto-increment for read/write
#define _TCA8418_REG_CFG_GPI_E_CGF 0x40 // Event mode config
#define _TCA8418_REG_CFG_OVR_FLOW_M 0x20 // Overflow mode enable
#define _TCA8418_REG_CFG_INT_CFG 0x10 // Interrupt config
#define _TCA8418_REG_CFG_OVR_FLOW_IEN 0x08 // Overflow interrupt enable
#define _TCA8418_REG_CFG_K_LCK_IEN 0x04 // Keypad lock interrupt enable
#define _TCA8418_REG_CFG_GPI_IEN 0x02 // GPI interrupt enable
#define _TCA8418_REG_CFG_KE_IEN 0x01 // Key events interrupt enable
// FIELDS INT_STAT REGISTER 2
#define _TCA8418_REG_STAT_CAD_INT 0x10 // Ctrl-alt-del seq status
#define _TCA8418_REG_STAT_OVR_FLOW_INT 0x08 // Overflow interrupt status
#define _TCA8418_REG_STAT_K_LCK_INT 0x04 // Key lock interrupt status
#define _TCA8418_REG_STAT_GPI_INT 0x02 // GPI interrupt status
#define _TCA8418_REG_STAT_K_INT 0x01 // Key events interrupt status
// FIELDS KEY_LCK_EC REGISTER 3
#define _TCA8418_REG_LCK_EC_K_LCK_EN 0x40 // Key lock enable
#define _TCA8418_REG_LCK_EC_LCK_2 0x20 // Keypad lock status 2
#define _TCA8418_REG_LCK_EC_LCK_1 0x10 // Keypad lock status 1
#define _TCA8418_REG_LCK_EC_KLEC_3 0x08 // Key event count bit 3
#define _TCA8418_REG_LCK_EC_KLEC_2 0x04 // Key event count bit 2
#define _TCA8418_REG_LCK_EC_KLEC_1 0x02 // Key event count bit 1
#define _TCA8418_REG_LCK_EC_KLEC_0 0x01 // Key event count bit 0
TCA8418KeyboardBase::TCA8418KeyboardBase(uint8_t rows, uint8_t columns)
: rows(rows), columns(columns), state(Init), queue(""), m_wire(nullptr), m_addr(0), readCallback(nullptr),
writeCallback(nullptr)
{
}
void TCA8418KeyboardBase::begin(uint8_t addr, TwoWire *wire)
{
m_addr = addr;
m_wire = wire;
m_wire->begin();
reset();
}
void TCA8418KeyboardBase::begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr)
{
m_addr = addr;
m_wire = nullptr;
writeCallback = w;
readCallback = r;
reset();
}
void TCA8418KeyboardBase::reset()
{
LOG_DEBUG("TCA8418 Reset");
// GPIO
// set default all GIO pins to INPUT
writeRegister(TCA8418_REG_GPIO_DIR_1, 0x00);
writeRegister(TCA8418_REG_GPIO_DIR_2, 0x00);
writeRegister(TCA8418_REG_GPIO_DIR_3, 0x00);
// add all pins to key events
writeRegister(TCA8418_REG_GPI_EM_1, 0xFF);
writeRegister(TCA8418_REG_GPI_EM_2, 0xFF);
writeRegister(TCA8418_REG_GPI_EM_3, 0xFF);
// set all pins to FALLING interrupts
writeRegister(TCA8418_REG_GPIO_INT_LVL_1, 0x00);
writeRegister(TCA8418_REG_GPIO_INT_LVL_2, 0x00);
writeRegister(TCA8418_REG_GPIO_INT_LVL_3, 0x00);
// add all pins to interrupts
writeRegister(TCA8418_REG_GPIO_INT_EN_1, 0xFF);
writeRegister(TCA8418_REG_GPIO_INT_EN_2, 0xFF);
writeRegister(TCA8418_REG_GPIO_INT_EN_3, 0xFF);
// Set keyboard matrix size
matrix(rows, columns);
enableDebounce();
flush();
state = Idle;
}
bool TCA8418KeyboardBase::matrix(uint8_t rows, uint8_t columns)
{
if (rows < 1 || rows > 8 || columns < 1 || columns > 10)
return false;
// Setup the keypad matrix.
uint8_t mask = 0x00;
for (int r = 0; r < rows; r++) {
mask <<= 1;
mask |= 1;
}
writeRegister(TCA8418_REG_KP_GPIO_1, mask);
mask = 0x00;
for (int c = 0; c < columns && c < 8; c++) {
mask <<= 1;
mask |= 1;
}
writeRegister(TCA8418_REG_KP_GPIO_2, mask);
if (columns > 8) {
if (columns == 9)
mask = 0x01;
else
mask = 0x03;
writeRegister(TCA8418_REG_KP_GPIO_3, mask);
}
return true;
}
uint8_t TCA8418KeyboardBase::keyCount() const
{
uint8_t eventCount = readRegister(TCA8418_REG_KEY_LCK_EC);
eventCount &= 0x0F; // lower 4 bits only
return eventCount;
}
bool TCA8418KeyboardBase::hasEvent() const
{
return queue.length() > 0;
}
void TCA8418KeyboardBase::queueEvent(char next)
{
if (next == NONE) {
return;
}
queue.concat(next);
}
char TCA8418KeyboardBase::dequeueEvent()
{
if (queue.length() < 1) {
return NONE;
}
char next = queue.charAt(0);
queue.remove(0, 1);
return next;
}
void TCA8418KeyboardBase::trigger()
{
if (keyCount() == 0) {
return;
}
if (state != Init) {
// Read the key register
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A);
uint8_t key = k & 0x7F;
if (k & 0x80) {
if (state == Idle)
pressed(key);
return;
} else {
if (state == Held) {
released();
}
state = Idle;
return;
}
} else {
reset();
}
}
void TCA8418KeyboardBase::pressed(uint8_t key)
{
// must be defined in derived class
LOG_ERROR("pressed() not implemented in derived class");
}
void TCA8418KeyboardBase::released()
{
// must be defined in derived class
LOG_ERROR("released() not implemented in derived class");
}
uint8_t TCA8418KeyboardBase::flush()
{
// Flush key events
uint8_t count = 0;
while (readRegister(TCA8418_REG_KEY_EVENT_A) != 0)
count++;
// Flush gpio events
readRegister(TCA8418_REG_GPIO_INT_STAT_1);
readRegister(TCA8418_REG_GPIO_INT_STAT_2);
readRegister(TCA8418_REG_GPIO_INT_STAT_3);
// Clear INT_STAT register
writeRegister(TCA8418_REG_INT_STAT, 3);
return count;
}
uint8_t TCA8418KeyboardBase::digitalRead(uint8_t pinnum) const
{
if (pinnum > TCA8418_COL9)
return 0xFF;
uint8_t reg = TCA8418_REG_GPIO_DAT_STAT_1 + pinnum / 8;
uint8_t mask = (1 << (pinnum % 8));
// Level 0 = low other = high
uint8_t value = readRegister(reg);
if (value & mask)
return HIGH;
return LOW;
}
bool TCA8418KeyboardBase::digitalWrite(uint8_t pinnum, uint8_t level)
{
if (pinnum > TCA8418_COL9)
return false;
uint8_t reg = TCA8418_REG_GPIO_DAT_OUT_1 + pinnum / 8;
uint8_t mask = (1 << (pinnum % 8));
// Level 0 = low other = high
uint8_t value = readRegister(reg);
if (level == LOW)
value &= ~mask;
else
value |= mask;
writeRegister(reg, value);
return true;
}
bool TCA8418KeyboardBase::pinMode(uint8_t pinnum, uint8_t mode)
{
if (pinnum > TCA8418_COL9)
return false;
uint8_t idx = pinnum / 8;
uint8_t reg = TCA8418_REG_GPIO_DIR_1 + idx;
uint8_t mask = (1 << (pinnum % 8));
// Mode 0 = input 1 = output
uint8_t value = readRegister(reg);
if (mode == OUTPUT)
value |= mask;
else
value &= ~mask;
writeRegister(reg, value);
// Pullup 0 = enabled 1 = disabled
reg = TCA8418_REG_GPIO_PULL_1 + idx;
value = readRegister(reg);
if (mode == INPUT_PULLUP)
value &= ~mask;
else
value |= mask;
writeRegister(reg, value);
return true;
}
bool TCA8418KeyboardBase::pinIRQMode(uint8_t pinnum, uint8_t mode)
{
if (pinnum > TCA8418_COL9)
return false;
if ((mode != RISING) && (mode != FALLING))
return false;
// Mode 0 = falling 1 = rising
uint8_t idx = pinnum / 8;
uint8_t reg = TCA8418_REG_GPIO_INT_LVL_1 + idx;
uint8_t mask = (1 << (pinnum % 8));
uint8_t value = readRegister(reg);
if (mode == RISING)
value |= mask;
else
value &= ~mask;
writeRegister(reg, value);
// Enable interrupt
reg = TCA8418_REG_GPIO_INT_EN_1 + idx;
value = readRegister(reg);
value |= mask;
writeRegister(reg, value);
return true;
}
void TCA8418KeyboardBase::enableInterrupts()
{
uint8_t value = readRegister(TCA8418_REG_CFG);
value |= (_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN);
writeRegister(TCA8418_REG_CFG, value);
};
void TCA8418KeyboardBase::disableInterrupts()
{
uint8_t value = readRegister(TCA8418_REG_CFG);
value &= ~(_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN);
writeRegister(TCA8418_REG_CFG, value);
};
void TCA8418KeyboardBase::enableMatrixOverflow()
{
uint8_t value = readRegister(TCA8418_REG_CFG);
value |= _TCA8418_REG_CFG_OVR_FLOW_M;
writeRegister(TCA8418_REG_CFG, value);
};
void TCA8418KeyboardBase::disableMatrixOverflow()
{
uint8_t value = readRegister(TCA8418_REG_CFG);
value &= ~_TCA8418_REG_CFG_OVR_FLOW_M;
writeRegister(TCA8418_REG_CFG, value);
};
void TCA8418KeyboardBase::enableDebounce()
{
writeRegister(TCA8418_REG_DEBOUNCE_DIS_1, 0x00);
writeRegister(TCA8418_REG_DEBOUNCE_DIS_2, 0x00);
writeRegister(TCA8418_REG_DEBOUNCE_DIS_3, 0x00);
}
void TCA8418KeyboardBase::disableDebounce()
{
writeRegister(TCA8418_REG_DEBOUNCE_DIS_1, 0xFF);
writeRegister(TCA8418_REG_DEBOUNCE_DIS_2, 0xFF);
writeRegister(TCA8418_REG_DEBOUNCE_DIS_3, 0xFF);
}
void TCA8418KeyboardBase::setBacklight(bool on) {}
uint8_t TCA8418KeyboardBase::readRegister(uint8_t reg) const
{
if (m_wire) {
m_wire->beginTransmission(m_addr);
m_wire->write(reg);
m_wire->endTransmission();
m_wire->requestFrom(m_addr, (uint8_t)1);
if (m_wire->available() < 1)
return 0;
return m_wire->read();
}
if (readCallback) {
uint8_t data;
readCallback(m_addr, reg, &data, 1);
return data;
}
return 0;
}
void TCA8418KeyboardBase::writeRegister(uint8_t reg, uint8_t value)
{
uint8_t data[2];
data[0] = reg;
data[1] = value;
if (m_wire) {
m_wire->beginTransmission(m_addr);
m_wire->write(data, sizeof(uint8_t) * 2);
m_wire->endTransmission();
}
if (writeCallback) {
writeCallback(m_addr, data[0], &(data[1]), 1);
}
}

View File

@@ -1,170 +0,0 @@
// Based on the MPR121 Keyboard and Adafruit TCA8418 library
#include "configuration.h"
#include <Wire.h>
/**
* @brief TCA8418KeyboardBase is the base class for TCA8418 keyboard handling.
* It provides basic functionality for reading key events, managing the keyboard matrix,
* and handling key states. It is designed to be extended for specific keyboard implementations.
* It supports both I2C communication and function pointers for custom I2C operations.
*/
class TCA8418KeyboardBase
{
public:
enum TCA8418Key : uint8_t {
NONE = 0x00,
BSP = 0x08,
TAB = 0x09,
SELECT = 0x0d,
ESC = 0x1b,
REBOOT = 0x90,
LEFT = 0xb4,
UP = 0xb5,
DOWN = 0xb6,
RIGHT = 0xb7,
BT_TOGGLE = 0xAA,
GPS_TOGGLE = 0x9E,
MUTE_TOGGLE = 0xAC,
SEND_PING = 0xAF,
BL_TOGGLE = 0xAB
};
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
TCA8418KeyboardBase(uint8_t rows, uint8_t columns);
virtual void begin(uint8_t addr = TCA8418_KB_ADDR, TwoWire *wire = &Wire);
virtual void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = TCA8418_KB_ADDR);
virtual void reset(void);
virtual void trigger(void);
virtual void setBacklight(bool on);
// Key events available
virtual bool hasEvent(void) const;
virtual char dequeueEvent(void);
protected:
enum KeyState { Init, Idle, Held, Busy };
enum TCA8418Register : uint8_t {
TCA8418_REG_RESERVED = 0x00,
TCA8418_REG_CFG = 0x01,
TCA8418_REG_INT_STAT = 0x02,
TCA8418_REG_KEY_LCK_EC = 0x03,
TCA8418_REG_KEY_EVENT_A = 0x04,
TCA8418_REG_KEY_EVENT_B = 0x05,
TCA8418_REG_KEY_EVENT_C = 0x06,
TCA8418_REG_KEY_EVENT_D = 0x07,
TCA8418_REG_KEY_EVENT_E = 0x08,
TCA8418_REG_KEY_EVENT_F = 0x09,
TCA8418_REG_KEY_EVENT_G = 0x0A,
TCA8418_REG_KEY_EVENT_H = 0x0B,
TCA8418_REG_KEY_EVENT_I = 0x0C,
TCA8418_REG_KEY_EVENT_J = 0x0D,
TCA8418_REG_KP_LCK_TIMER = 0x0E,
TCA8418_REG_UNLOCK_1 = 0x0F,
TCA8418_REG_UNLOCK_2 = 0x10,
TCA8418_REG_GPIO_INT_STAT_1 = 0x11,
TCA8418_REG_GPIO_INT_STAT_2 = 0x12,
TCA8418_REG_GPIO_INT_STAT_3 = 0x13,
TCA8418_REG_GPIO_DAT_STAT_1 = 0x14,
TCA8418_REG_GPIO_DAT_STAT_2 = 0x15,
TCA8418_REG_GPIO_DAT_STAT_3 = 0x16,
TCA8418_REG_GPIO_DAT_OUT_1 = 0x17,
TCA8418_REG_GPIO_DAT_OUT_2 = 0x18,
TCA8418_REG_GPIO_DAT_OUT_3 = 0x19,
TCA8418_REG_GPIO_INT_EN_1 = 0x1A,
TCA8418_REG_GPIO_INT_EN_2 = 0x1B,
TCA8418_REG_GPIO_INT_EN_3 = 0x1C,
TCA8418_REG_KP_GPIO_1 = 0x1D,
TCA8418_REG_KP_GPIO_2 = 0x1E,
TCA8418_REG_KP_GPIO_3 = 0x1F,
TCA8418_REG_GPI_EM_1 = 0x20,
TCA8418_REG_GPI_EM_2 = 0x21,
TCA8418_REG_GPI_EM_3 = 0x22,
TCA8418_REG_GPIO_DIR_1 = 0x23,
TCA8418_REG_GPIO_DIR_2 = 0x24,
TCA8418_REG_GPIO_DIR_3 = 0x25,
TCA8418_REG_GPIO_INT_LVL_1 = 0x26,
TCA8418_REG_GPIO_INT_LVL_2 = 0x27,
TCA8418_REG_GPIO_INT_LVL_3 = 0x28,
TCA8418_REG_DEBOUNCE_DIS_1 = 0x29,
TCA8418_REG_DEBOUNCE_DIS_2 = 0x2A,
TCA8418_REG_DEBOUNCE_DIS_3 = 0x2B,
TCA8418_REG_GPIO_PULL_1 = 0x2C,
TCA8418_REG_GPIO_PULL_2 = 0x2D,
TCA8418_REG_GPIO_PULL_3 = 0x2E
};
// Pin IDs for matrix rows/columns
enum TCA8418PinId : uint8_t {
TCA8418_ROW0, // Pin ID for row 0
TCA8418_ROW1, // Pin ID for row 1
TCA8418_ROW2, // Pin ID for row 2
TCA8418_ROW3, // Pin ID for row 3
TCA8418_ROW4, // Pin ID for row 4
TCA8418_ROW5, // Pin ID for row 5
TCA8418_ROW6, // Pin ID for row 6
TCA8418_ROW7, // Pin ID for row 7
TCA8418_COL0, // Pin ID for column 0
TCA8418_COL1, // Pin ID for column 1
TCA8418_COL2, // Pin ID for column 2
TCA8418_COL3, // Pin ID for column 3
TCA8418_COL4, // Pin ID for column 4
TCA8418_COL5, // Pin ID for column 5
TCA8418_COL6, // Pin ID for column 6
TCA8418_COL7, // Pin ID for column 7
TCA8418_COL8, // Pin ID for column 8
TCA8418_COL9 // Pin ID for column 9
};
virtual void pressed(uint8_t key);
virtual void released(void);
virtual void queueEvent(char);
virtual ~TCA8418KeyboardBase() {}
protected:
// Set the size of the keypad matrix
// All other rows and columns are set as inputs.
bool matrix(uint8_t rows, uint8_t columns);
uint8_t keyCount(void) const;
// Flush all events in the FIFO buffer + GPIO events.
uint8_t flush(void);
// debounce keys.
void enableDebounce();
void disableDebounce();
// enable / disable interrupts for matrix and GPI pins
void enableInterrupts();
void disableInterrupts();
// ignore key events when FIFO buffer is full or not.
void enableMatrixOverflow();
void disableMatrixOverflow();
uint8_t digitalRead(uint8_t pinnum) const;
bool digitalWrite(uint8_t pinnum, uint8_t level);
bool pinMode(uint8_t pinnum, uint8_t mode);
bool pinIRQMode(uint8_t pinnum, uint8_t mode); // MODE FALLING or RISING
uint8_t readRegister(uint8_t reg) const;
void writeRegister(uint8_t reg, uint8_t value);
protected:
uint8_t rows;
uint8_t columns;
KeyState state;
String queue;
private:
TwoWire *m_wire;
uint8_t m_addr;
i2c_com_fptr_t readCallback;
i2c_com_fptr_t writeCallback;
};

View File

@@ -1,196 +0,0 @@
#if defined(T_DECK_PRO)
#include "TDeckProKeyboard.h"
#define _TCA8418_COLS 10
#define _TCA8418_ROWS 4
#define _TCA8418_NUM_KEYS 35
#define _TCA8418_MULTI_TAP_THRESHOLD 1500
using Key = TCA8418KeyboardBase::TCA8418Key;
constexpr uint8_t modifierRightShiftKey = 31 - 1; // keynum -1
constexpr uint8_t modifierRightShift = 0b0001;
constexpr uint8_t modifierLeftShiftKey = 35 - 1;
constexpr uint8_t modifierLeftShift = 0b0001;
constexpr uint8_t modifierSymKey = 32 - 1;
constexpr uint8_t modifierSym = 0b0010;
constexpr uint8_t modifierAltKey = 30 - 1;
constexpr uint8_t modifierAlt = 0b0100;
// Num chars per key, Modulus for rotating through characters
static uint8_t TDeckProTapMod[_TCA8418_NUM_KEYS] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5};
static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = {
{'p', 'P', '@', 0x00, Key::SEND_PING},
{'o', 'O', '+'},
{'i', 'I', '-'},
{'u', 'U', '_'},
{'y', 'Y', ')'},
{'t', 'T', '(', 0x00, Key::TAB},
{'r', 'R', '3'},
{'e', 'E', '2', 0x00, Key::UP},
{'w', 'W', '1'},
{'q', 'Q', '#', 0x00, Key::ESC}, // p, o, i, u, y, t, r, e, w, q
{Key::BSP, 0x00, 0x00},
{'l', 'L', '"'},
{'k', 'K', '\''},
{'j', 'J', ';'},
{'h', 'H', ':'},
{'g', 'G', '/', 0x00, Key::GPS_TOGGLE},
{'f', 'F', '6', 0x00, Key::RIGHT},
{'d', 'D', '5'},
{'s', 'S', '4', 0x00, Key::LEFT},
{'a', 'A', '*'}, // bsp, l, k, j, h, g, f, d, s, a
{0x0d, 0x00, 0x00},
{'$', 0x00, 0x00},
{'m', 'M', '.', 0x00, Key::MUTE_TOGGLE},
{'n', 'N', ','},
{'b', 'B', '!', 0x00, Key::BL_TOGGLE},
{'v', 'V', '?'},
{'c', 'C', '9'},
{'x', 'X', '8', 0x00, Key::DOWN},
{'z', 'Z', '7'},
{0x00, 0x00, 0x00}, // Ent, $, m, n, b, v, c, x, z, alt
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x20, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift
};
TDeckProKeyboard::TDeckProKeyboard()
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
last_tap(0L), char_idx(0), tap_interval(0)
{
}
void TDeckProKeyboard::reset()
{
TCA8418KeyboardBase::reset();
pinMode(KB_BL_PIN, OUTPUT);
setBacklight(false);
}
// handle multi-key presses (shift and alt)
void TDeckProKeyboard::trigger()
{
uint8_t count = keyCount();
if (count == 0)
return;
for (uint8_t i = 0; i < count; ++i) {
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
uint8_t key = k & 0x7F;
if (k & 0x80) {
pressed(key);
} else {
released();
state = Idle;
}
}
}
void TDeckProKeyboard::pressed(uint8_t key)
{
if (state == Init || state == Busy) {
return;
}
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
modifierFlag = 0;
}
uint8_t next_key = 0;
int row = (key - 1) / 10;
int col = (key - 1) % 10;
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
return; // Invalid key
}
next_key = row * _TCA8418_COLS + col;
state = Held;
uint32_t now = millis();
tap_interval = now - last_tap;
updateModifierFlag(next_key);
if (isModifierKey(next_key)) {
last_modifier_time = now;
}
if (tap_interval < 0) {
last_tap = 0;
state = Busy;
return;
}
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
char_idx = 0;
} else {
char_idx += 1;
}
last_key = next_key;
last_tap = now;
}
void TDeckProKeyboard::released()
{
if (state != Held) {
return;
}
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
last_key = -1;
state = Idle;
return;
}
uint32_t now = millis();
last_tap = now;
if (TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]] == Key::BL_TOGGLE) {
toggleBacklight();
return;
}
queueEvent(TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]]);
if (isModifierKey(last_key) == false)
modifierFlag = 0;
}
void TDeckProKeyboard::setBacklight(bool on)
{
if (on) {
digitalWrite(KB_BL_PIN, HIGH);
} else {
digitalWrite(KB_BL_PIN, LOW);
}
}
void TDeckProKeyboard::toggleBacklight(void)
{
digitalWrite(KB_BL_PIN, !digitalRead(KB_BL_PIN));
}
void TDeckProKeyboard::updateModifierFlag(uint8_t key)
{
if (key == modifierRightShiftKey) {
modifierFlag ^= modifierRightShift;
} else if (key == modifierLeftShiftKey) {
modifierFlag ^= modifierLeftShift;
} else if (key == modifierSymKey) {
modifierFlag ^= modifierSym;
} else if (key == modifierAltKey) {
modifierFlag ^= modifierAlt;
}
}
bool TDeckProKeyboard::isModifierKey(uint8_t key)
{
return (key == modifierRightShiftKey || key == modifierLeftShiftKey || key == modifierAltKey || key == modifierSymKey);
}
#endif // T_DECK_PRO

View File

@@ -1,27 +0,0 @@
#include "TCA8418KeyboardBase.h"
class TDeckProKeyboard : public TCA8418KeyboardBase
{
public:
TDeckProKeyboard();
void reset(void) override;
void trigger(void) override;
void setBacklight(bool on) override;
protected:
void pressed(uint8_t key) override;
void released(void) override;
void updateModifierFlag(uint8_t key);
bool isModifierKey(uint8_t key);
void toggleBacklight(void);
private:
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
uint32_t last_modifier_time; // Timestamp of the last modifier key press
int8_t last_key;
int8_t next_key;
uint32_t last_tap;
uint8_t char_idx;
int32_t tap_interval;
};

View File

@@ -1,12 +0,0 @@
#include "TCA8418KeyboardBase.h"
class TLoraPagerKeyboard : public TCA8418KeyboardBase
{
public:
TLoraPagerKeyboard();
void setBacklight(bool on) override{};
protected:
void pressed(uint8_t key) override{};
void released(void) override{};
};

View File

@@ -67,5 +67,4 @@ void CardKbI2cImpl::init()
}
#endif
inputBroker->registerSource(this);
kb_found = true;
}

View File

@@ -3,26 +3,10 @@
#include "detect/ScanI2C.h"
#include "detect/ScanI2CTwoWire.h"
#if defined(T_DECK_PRO)
#include "TDeckProKeyboard.h"
#elif defined(T_LORA_PAGER)
#include "TLoraPagerKeyboard.h"
#else
#include "TCA8418Keyboard.h"
#endif
extern ScanI2C::DeviceAddress cardkb_found;
extern uint8_t kb_model;
KbI2cBase::KbI2cBase(const char *name)
: concurrency::OSThread(name),
#if defined(T_DECK_PRO)
TCAKeyboard(*(new TDeckProKeyboard()))
#elif defined(T_LORA_PAGER)
TCAKeyboard(*(new TLoraPagerKeyboard()))
#else
TCAKeyboard(*(new TCA8418Keyboard()))
#endif
KbI2cBase::KbI2cBase(const char *name) : concurrency::OSThread(name)
{
this->_originName = name;
}
@@ -59,8 +43,8 @@ int32_t KbI2cBase::runOnce()
if (cardkb_found.address == MPR121_KB_ADDR) {
MPRkeyboard.begin(MPR121_KB_ADDR, &Wire1);
}
if (cardkb_found.address == TCA8418_KB_ADDR) {
TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire1);
if (cardkb_found.address == XPOWERS_AXP192_AXP2101_ADDRESS) {
TCAKeyboard.begin(XPOWERS_AXP192_AXP2101_ADDRESS, &Wire1);
}
break;
#endif
@@ -74,8 +58,8 @@ int32_t KbI2cBase::runOnce()
if (cardkb_found.address == MPR121_KB_ADDR) {
MPRkeyboard.begin(MPR121_KB_ADDR, &Wire);
}
if (cardkb_found.address == TCA8418_KB_ADDR) {
TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire);
if (cardkb_found.address == XPOWERS_AXP192_AXP2101_ADDRESS) {
TCAKeyboard.begin(XPOWERS_AXP192_AXP2101_ADDRESS, &Wire);
}
break;
case ScanI2C::NO_I2C:
@@ -257,65 +241,41 @@ int32_t KbI2cBase::runOnce()
e.kbchar = 0x00;
e.source = this->_originName;
switch (nextEvent) {
case TCA8418KeyboardBase::NONE:
case _TCA8418_NONE:
e.inputEvent = INPUT_BROKER_NONE;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::REBOOT:
case _TCA8418_REBOOT:
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_MSG_REBOOT;
break;
case TCA8418KeyboardBase::LEFT:
case _TCA8418_LEFT:
e.inputEvent = INPUT_BROKER_LEFT;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::UP:
case _TCA8418_UP:
e.inputEvent = INPUT_BROKER_UP;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::DOWN:
case _TCA8418_DOWN:
e.inputEvent = INPUT_BROKER_DOWN;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::RIGHT:
case _TCA8418_RIGHT:
e.inputEvent = INPUT_BROKER_RIGHT;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::BSP:
case _TCA8418_BSP:
e.inputEvent = INPUT_BROKER_BACK;
e.kbchar = 0x08;
break;
case TCA8418KeyboardBase::SELECT:
case _TCA8418_SELECT:
e.inputEvent = INPUT_BROKER_SELECT;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::ESC:
case _TCA8418_ESC:
e.inputEvent = INPUT_BROKER_CANCEL;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::GPS_TOGGLE:
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_GPS_TOGGLE;
break;
case TCA8418KeyboardBase::SEND_PING:
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_SEND_PING;
break;
case TCA8418KeyboardBase::MUTE_TOGGLE:
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE;
break;
case TCA8418KeyboardBase::BT_TOGGLE:
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE;
break;
case TCA8418KeyboardBase::BL_TOGGLE:
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE;
break;
case TCA8418KeyboardBase::TAB:
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_MSG_TAB;
e.kbchar = 0;
break;
default:
if (nextEvent > 127) {
@@ -331,7 +291,6 @@ int32_t KbI2cBase::runOnce()
LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar);
this->notifyObservers(&e);
}
TCAKeyboard.trigger();
}
break;
}

View File

@@ -3,11 +3,10 @@
#include "BBQ10Keyboard.h"
#include "InputBroker.h"
#include "MPR121Keyboard.h"
#include "TCA8418Keyboard.h"
#include "Wire.h"
#include "concurrency/OSThread.h"
class TCA8418KeyboardBase;
class KbI2cBase : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
@@ -23,6 +22,6 @@ class KbI2cBase : public Observable<const InputEvent *>, public concurrency::OST
BBQ10Keyboard Q10keyboard;
MPR121Keyboard MPRkeyboard;
TCA8418KeyboardBase &TCAKeyboard;
TCA8418Keyboard TCAKeyboard;
bool is_sym = false;
};

View File

@@ -22,6 +22,11 @@
#include "error.h"
#include "power.h"
#ifdef SENSECAP_INDICATOR // on the indicator run the additional serial port for the RP2040
#include "IndicatorSerial.h"
#include "mesh/comms/FakeI2C.h"
#endif
#if !MESHTASTIC_EXCLUDE_I2C
#include "detect/ScanI2CTwoWire.h"
#include <Wire.h>
@@ -33,15 +38,12 @@
#include "mesh/generated/meshtastic/config.pb.h"
#include "meshUtils.h"
#include "modules/Modules.h"
#include "shutdown.h"
#include "sleep.h"
#include "target_specific.h"
#include <memory>
#include <utility>
#ifdef ELECROW_ThinkNode_M5
PCA9557 io(0x18, &Wire);
#endif
#ifdef ARCH_ESP32
#include "freertosinc.h"
#if !MESHTASTIC_EXCLUDE_WEBSERVER
@@ -289,7 +291,7 @@ void lateInitVariant() {}
*/
void printInfo()
{
LOG_INFO("S:B:%d,%s,%s,%s", HW_VENDOR, optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
LOG_INFO("S:B:%d,%s", HW_VENDOR, optstr(APP_VERSION));
}
#ifndef PIO_UNIT_TESTING
void setup()
@@ -300,15 +302,6 @@ void setup()
digitalWrite(PIN_POWER_EN, HIGH);
#endif
#if defined(ELECROW_ThinkNode_M5)
Wire.begin(48, 47);
io.pinMode(PCA_PIN_EINK_EN, OUTPUT);
io.pinMode(PCA_PIN_POWER_EN, OUTPUT);
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
io.digitalWrite(PCA_PIN_POWER_EN, HIGH);
// io.pinMode(C2_PIN, OUTPUT);
#endif
#ifdef LED_POWER
pinMode(LED_POWER, OUTPUT);
digitalWrite(LED_POWER, LED_STATE_ON);
@@ -347,15 +340,6 @@ void setup()
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH);
delay(100);
#elif defined(T_DECK_PRO)
pinMode(LORA_EN, OUTPUT);
digitalWrite(LORA_EN, HIGH);
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
pinMode(PIN_EINK_CS, OUTPUT);
digitalWrite(PIN_EINK_CS, HIGH);
#endif
concurrency::hasBeenSetup = true;
@@ -478,7 +462,11 @@ void setup()
fsInit();
#if !MESHTASTIC_EXCLUDE_I2C
#if defined(I2C_SDA1) && defined(ARCH_RP2040)
// The Sensecap Indicator hast I2C on the secondary MCU. Tunnel this as wire1
#if defined(SENSECAP_INDICATOR)
FakeI2C& Wire1 = *FakeWire;
Wire1.begin();
#elif defined(I2C_SDA1) && defined(ARCH_RP2040)
Wire1.setSDA(I2C_SDA1);
Wire1.setSCL(I2C_SCL1);
Wire1.begin();
@@ -536,11 +524,27 @@ void setup()
LOG_INFO("Scan for i2c devices");
#endif
#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2))
#if defined(SENSECAP_INDICATOR)
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1);
#elif defined(I2C_SDA1) && defined(ARCH_RP2040)
Wire1.setSDA(I2C_SDA1);
Wire1.setSCL(I2C_SCL1);
Wire1.begin();
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1);
#elif defined(I2C_SDA1) && !defined(ARCH_RP2040)
Wire1.begin(I2C_SDA1, I2C_SCL1);
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1);
#elif defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1);
#endif
#if defined(I2C_SDA)
#if defined(I2C_SDA) && defined(ARCH_RP2040)
Wire.setSDA(I2C_SDA);
Wire.setSCL(I2C_SCL);
Wire.begin();
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
#elif defined(I2C_SDA) && !defined(ARCH_RP2040)
Wire.begin(I2C_SDA, I2C_SCL);
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
#elif defined(ARCH_PORTDUINO)
if (settingsStrings[i2cdev] != "") {
@@ -755,6 +759,15 @@ void setup()
} else
router = new ReliableRouter();
// If we have an indicator, start process to service secondary port
#ifdef SENSECAP_INDICATOR
#if SENSOR_PORT_NUM == 2
sensecapIndicator = new SensecapIndicator(Serial2);
#elif SENSOR_PORT_NUM == 1
sensecapIndicator = new SensecapIndicator(Serial1);
#endif
#endif
// only play start melody when role is not tracker or sensor
if (config.power.is_power_saving == true &&
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TRACKER,
@@ -915,20 +928,14 @@ void setup()
service = new MeshService();
service->init();
if (nodeDB->keyIsLowEntropy) {
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
}
// Now that the mesh service is created, create any modules
setupModules();
// warn the user about a low entropy key
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
LOG_WARN(LOW_ENTROPY_WARNING);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->time = getValidTime(RTCQualityFromNet);
sprintf(cn->message, LOW_ENTROPY_WARNING);
service->sendClientNotification(cn);
nodeDB->hasWarned = true;
}
// buttons are now inputBroker, so have to come after setupModules
#if HAS_BUTTON
int pullup_sense = 0;
@@ -1069,9 +1076,8 @@ void setup()
mainDelay.interruptFromISR(&higherWake);
};
userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS;
userConfigNoScreen.longPress = INPUT_BROKER_NONE;
userConfigNoScreen.longPressTime = 500;
userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN;
userConfigNoScreen.longPress = INPUT_BROKER_SHUTDOWN;
userConfigNoScreen.longPressTime = 5000;
userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING;
userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE;
UserButtonThread->initButton(userConfigNoScreen);
@@ -1548,7 +1554,7 @@ void loop()
#ifdef ARCH_NRF52
nrf52Loop();
#endif
power->powerCommandsCheck();
powerCommandsCheck();
#ifdef DEBUG_STACK
static uint32_t lastPrint = 0;

View File

@@ -51,11 +51,6 @@ extern Adafruit_DRV2605 drv;
extern AudioThread *audioThread;
#endif
#ifdef ELECROW_ThinkNode_M5
#include <PCA9557.h>
extern PCA9557 io;
#endif
#ifdef HAS_UDP_MULTICAST
#include "mesh/udp/UdpMulticastHandler.h"
extern UdpMulticastHandler *udpHandler;

View File

@@ -10,10 +10,6 @@
#include "memGet.h"
#include "configuration.h"
#ifdef ARCH_STM32WL
#include <malloc.h>
#endif
MemGet memGet;
/**
@@ -28,9 +24,6 @@ uint32_t MemGet::getFreeHeap()
return dbgHeapFree();
#elif defined(ARCH_RP2040)
return rp2040.getFreeHeap();
#elif defined(ARCH_STM32WL)
struct mallinfo m = mallinfo();
return m.fordblks; // Total free space (bytes)
#else
// this platform does not have heap management function implemented
return UINT32_MAX;
@@ -49,9 +42,6 @@ uint32_t MemGet::getHeapSize()
return dbgHeapTotal();
#elif defined(ARCH_RP2040)
return rp2040.getTotalHeap();
#elif defined(ARCH_STM32WL)
struct mallinfo m = mallinfo();
return m.arena; // Non-mmapped space allocated (bytes)
#else
// this platform does not have heap management function implemented
return UINT32_MAX;

View File

@@ -24,11 +24,6 @@
#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
#define min_neighbor_info_broadcast_secs 4 * 60 * 60
#define default_map_publish_interval_secs 60 * 60
#ifdef USERPREFS_RINGTONE_NAG_SECS
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
#else
#define default_ringtone_nag_secs 60
#endif
#define default_mqtt_address "mqtt.meshtastic.org"
#define default_mqtt_username "meshdev"

View File

@@ -0,0 +1,154 @@
#ifdef SENSECAP_INDICATOR
#include "IndicatorSerial.h"
#include "mesh/comms/FakeI2C.h"
#include "mesh/comms/FakeUART.h"
#include <HardwareSerial.h>
#include <pb_decode.h>
#include <pb_encode.h>
SensecapIndicator *sensecapIndicator;
SensecapIndicator::SensecapIndicator(HardwareSerial &serial) : OSThread("SensecapIndicator")
{
if (!running) {
_serial = &serial;
_serial->setRxBufferSize(PB_BUFSIZE);
_serial->setPins(SENSOR_RP2040_RXD, SENSOR_RP2040_TXD);
_serial->begin(SENSOR_BAUD_RATE);
running = true;
LOG_DEBUG("Start indicator communication thread");
}
}
int32_t SensecapIndicator::runOnce()
{
if (running) {
size_t bytes_read = 0;
// See if there are any more bytes to add to our buffer.
size_t space_left = PB_BUFSIZE - pb_rx_size;
bytes_read = serial_check((char *)pb_rx_buf + pb_rx_size, space_left);
pb_rx_size += bytes_read;
check_packet();
return (10);
} else {
LOG_DEBUG("Not running");
return (1000);
}
}
bool SensecapIndicator::send_uplink(meshtastic_InterdeviceMessage message)
{
pb_tx_buf[0] = MT_MAGIC_0;
pb_tx_buf[1] = MT_MAGIC_1;
pb_ostream_t stream = pb_ostream_from_buffer(pb_tx_buf + MT_HEADER_SIZE, PB_BUFSIZE);
if (!pb_encode(&stream, meshtastic_InterdeviceMessage_fields, &message)) {
LOG_DEBUG("pb_encode failed");
return false;
}
// Store the payload length in the header
pb_tx_buf[2] = stream.bytes_written / 256;
pb_tx_buf[3] = stream.bytes_written % 256;
bool rv = send((const char *)pb_tx_buf, MT_HEADER_SIZE + stream.bytes_written);
return rv;
}
size_t SensecapIndicator::serial_check(char *buf, size_t space_left)
{
size_t bytes_read = 0;
while (_serial->available()) {
char c = _serial->read();
*buf++ = c;
if (++bytes_read >= space_left) {
LOG_DEBUG("Serial overflow: %d > %d", bytes_read, space_left);
break;
}
}
return bytes_read;
}
void SensecapIndicator::check_packet()
{
if (pb_rx_size < MT_HEADER_SIZE) {
// We don't even have a header yet
delay(NO_NEWS_PAUSE);
return;
}
if (pb_rx_buf[0] != MT_MAGIC_0 || pb_rx_buf[1] != MT_MAGIC_1) {
LOG_DEBUG("Got bad magic");
memset(pb_rx_buf, 0, PB_BUFSIZE);
pb_rx_size = 0;
return;
}
uint16_t payload_len = pb_rx_buf[2] << 8 | pb_rx_buf[3];
if (payload_len > PB_BUFSIZE) {
LOG_DEBUG("Got packet claiming to be ridiculous length");
return;
}
if ((size_t)(payload_len + 4) > pb_rx_size) {
delay(NO_NEWS_PAUSE);
return;
}
// We have a complete packet, handle it
handle_packet(payload_len);
}
bool SensecapIndicator::handle_packet(size_t payload_len)
{
meshtastic_InterdeviceMessage message = meshtastic_InterdeviceMessage_init_zero;
// Decode the protobuf and shift forward any remaining bytes in the buffer
// (which, if present, belong to the packet that we're going to process on the
// next loop)
pb_istream_t stream = pb_istream_from_buffer(pb_rx_buf + MT_HEADER_SIZE, payload_len);
bool status = pb_decode(&stream, meshtastic_InterdeviceMessage_fields, &message);
memmove(pb_rx_buf, pb_rx_buf + MT_HEADER_SIZE + payload_len, PB_BUFSIZE - MT_HEADER_SIZE - payload_len);
pb_rx_size -= MT_HEADER_SIZE + payload_len;
if (!status) {
LOG_DEBUG("Decoding failed");
return false;
}
switch (message.which_data) {
case meshtastic_InterdeviceMessage_i2c_response_tag:
LOG_DEBUG("Got I2C response");
if (message.data.i2c_response.status != meshtastic_I2CResponse_Status_OK) {
LOG_DEBUG("I2C response error: %d", message.data.i2c_response.status);
return false;
}
// send I2C response to FakeI2C
FakeWire->ingest(message.data.i2c_response);
return true;
break;
case meshtastic_InterdeviceMessage_nmea_tag:
// send String to NMEA processing
FakeSerial->stuff_buffer(message.data.nmea, strlen(message.data.nmea));
return true;
break;
default:
// the other messages really only flow downstream
LOG_DEBUG("Got a message of unexpected type");
return false;
}
}
bool SensecapIndicator::send(const char *buf, size_t len)
{
size_t wrote = _serial->write(buf, len);
if (wrote == len)
return true;
return false;
}
#endif // SENSECAP_INDICATOR

View File

@@ -0,0 +1,47 @@
#pragma once
#ifndef INDICATORSERIAL_H
#define INDICATORSERIAL_H
#ifdef SENSECAP_INDICATOR
#include "concurrency/OSThread.h"
#include "configuration.h"
#include "mesh/generated/meshtastic/interdevice.pb.h"
// Magic number at the start of all MT packets
#define MT_MAGIC_0 0x94
#define MT_MAGIC_1 0xc3
// The header is the magic number plus a 16-bit payload-length field
#define MT_HEADER_SIZE 4
// Wait this many msec if there's nothing new on the channel
#define NO_NEWS_PAUSE 25
#define PB_BUFSIZE meshtastic_InterdeviceMessage_size + MT_HEADER_SIZE
class SensecapIndicator : public concurrency::OSThread
{
public:
SensecapIndicator(HardwareSerial &serial);
int32_t runOnce() override;
bool send_uplink(meshtastic_InterdeviceMessage message);
private:
pb_byte_t pb_tx_buf[PB_BUFSIZE];
pb_byte_t pb_rx_buf[PB_BUFSIZE];
size_t pb_rx_size = 0; // Number of bytes currently in the buffer
HardwareSerial *_serial = &Serial2;
bool running = false;
size_t serial_check(char *buf, size_t space_left);
void check_packet();
bool handle_packet(size_t payload_len);
bool send(const char *buf, size_t len);
};
extern SensecapIndicator *sensecapIndicator;
#endif // SENSECAP_INDICATOR
#endif // INDICATORSERIAL_H

View File

@@ -121,7 +121,7 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool t
bool MeshPacketQueue::find(const NodeNum from, const PacketId id)
{
for (auto it = queue.begin(); it != queue.end(); it++) {
const auto *p = *it;
const auto p = (*it);
if (getFrom(p) == from && p->id == id) {
return true;
}

View File

@@ -16,7 +16,6 @@
#include "meshUtils.h"
#include "modules/NodeInfoModule.h"
#include "modules/PositionModule.h"
#include "modules/RoutingModule.h"
#include "power.h"
#include <assert.h>
#include <string>
@@ -334,21 +333,6 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage
fromNum++;
}
void MeshService::sendRoutingErrorResponse(meshtastic_Routing_Error error, const meshtastic_MeshPacket *mp)
{
if (!mp) {
LOG_WARN("Cannot send routing error response: null packet");
return;
}
// Use the routing module to send the error response
if (routingModule) {
routingModule->sendAckNak(error, mp->from, mp->id, mp->channel);
} else {
LOG_ERROR("Cannot send routing error response: no routing module");
}
}
void MeshService::sendClientNotification(meshtastic_ClientNotification *n)
{
LOG_DEBUG("Send client notification to phone");

View File

@@ -146,10 +146,7 @@ class MeshService
virtual void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m);
/// Send a ClientNotification to the phone
virtual void sendClientNotification(meshtastic_ClientNotification *cn);
/// Send an error response to the phone
void sendRoutingErrorResponse(meshtastic_Routing_Error error, const meshtastic_MeshPacket *mp);
void sendClientNotification(meshtastic_ClientNotification *cn);
bool isToPhoneQueueEmpty();

View File

@@ -264,12 +264,12 @@ NodeDB::NodeDB()
if (!owner.is_licensed && config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
bool keygenSuccess = false;
keyIsLowEntropy = checkLowEntropyPublicKey(config.security.public_key);
if (config.security.private_key.size == 32 && !keyIsLowEntropy) {
if (config.security.private_key.size == 32) {
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
keygenSuccess = true;
}
} else {
LOG_INFO("Generate new PKI keys");
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
keygenSuccess = true;
}
@@ -288,6 +288,16 @@ NodeDB::NodeDB()
crypto->setDHPrivateKey(config.security.private_key.bytes);
}
#endif
keyIsLowEntropy = checkLowEntropyPublicKey(config.security.public_key);
if (keyIsLowEntropy) {
LOG_WARN("Erasing low entropy keys");
config.security.private_key.size = 0;
memfll(config.security.private_key.bytes, '\0', sizeof(config.security.private_key.bytes));
config.security.public_key.size = 0;
memfll(config.security.public_key.bytes, '\0', sizeof(config.security.public_key.bytes));
owner.public_key.size = 0;
memfll(owner.public_key.bytes, '\0', sizeof(owner.public_key.bytes));
}
// Include our owner in the node db under our nodenum
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
info->user = TypeConversions::ConvertToUserLite(owner);
@@ -334,22 +344,6 @@ NodeDB::NodeDB()
config.device.node_info_broadcast_secs = MAX_INTERVAL;
if (config.position.position_broadcast_secs > MAX_INTERVAL)
config.position.position_broadcast_secs = MAX_INTERVAL;
if (config.position.gps_update_interval > MAX_INTERVAL)
config.position.gps_update_interval = MAX_INTERVAL;
if (config.position.gps_attempt_time > MAX_INTERVAL)
config.position.gps_attempt_time = MAX_INTERVAL;
if (config.position.position_flags > MAX_INTERVAL)
config.position.position_flags = MAX_INTERVAL;
if (config.position.rx_gpio > MAX_INTERVAL)
config.position.rx_gpio = MAX_INTERVAL;
if (config.position.tx_gpio > MAX_INTERVAL)
config.position.tx_gpio = MAX_INTERVAL;
if (config.position.broadcast_smart_minimum_distance > MAX_INTERVAL)
config.position.broadcast_smart_minimum_distance = MAX_INTERVAL;
if (config.position.broadcast_smart_minimum_interval_secs > MAX_INTERVAL)
config.position.broadcast_smart_minimum_interval_secs = MAX_INTERVAL;
if (config.position.gps_en_gpio > MAX_INTERVAL)
config.position.gps_en_gpio = MAX_INTERVAL;
if (moduleConfig.neighbor_info.update_interval > MAX_INTERVAL)
moduleConfig.neighbor_info.update_interval = MAX_INTERVAL;
if (moduleConfig.telemetry.device_update_interval > MAX_INTERVAL)
@@ -375,14 +369,6 @@ 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)))
@@ -396,9 +382,6 @@ NodeDB::NodeDB()
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
config.position.gps_enabled = 0;
}
#ifdef USERPREFS_FIRMWARE_EDITION
myNodeInfo.firmware_edition = USERPREFS_FIRMWARE_EDITION;
#endif
#ifdef USERPREFS_FIXED_GPS
if (myNodeInfo.reboot_count == 1) { // Check if First boot ever or after Factory Reset.
meshtastic_Position fixedGPS = meshtastic_Position_init_default;
@@ -621,6 +604,11 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#ifdef PIN_GPS_EN
config.position.gps_en_gpio = PIN_GPS_EN;
#endif
#ifdef GPS_POWER_TOGGLE
config.device.disable_triple_click = false;
#else
config.device.disable_triple_click = true;
#endif
#if defined(USERPREFS_CONFIG_GPS_MODE)
config.position.gps_mode = USERPREFS_CONFIG_GPS_MODE;
#elif !HAS_GPS || GPS_DEFAULT_NOT_PRESENT
@@ -723,6 +711,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.display.screen_on_secs = 30;
config.display.wake_on_tap_or_motion = true;
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
if (WiFiOTA::isUpdated()) {
WiFiOTA::recoverConfig(&config.network);
@@ -781,14 +770,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.output_buzzer = PIN_BUZZER;
moduleConfig.external_notification.use_pwm = true;
moduleConfig.external_notification.alert_message_buzzer = true;
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
#endif
#if defined(PIN_VIBRATION)
moduleConfig.external_notification.enabled = true;
moduleConfig.external_notification.output_vibra = PIN_VIBRATION;
moduleConfig.external_notification.alert_message_vibra = true;
moduleConfig.external_notification.output_ms = 500;
moduleConfig.external_notification.nag_timeout = 2;
moduleConfig.external_notification.nag_timeout = 60;
#endif
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312)
// Default to RAK led pin 2 (blue)
@@ -797,7 +779,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.active = true;
moduleConfig.external_notification.alert_message = true;
moduleConfig.external_notification.output_ms = 1000;
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
moduleConfig.external_notification.nag_timeout = 60;
#endif
#ifdef HAS_I2S
@@ -806,10 +788,10 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.use_i2s_as_buzzer = true;
moduleConfig.external_notification.alert_message_buzzer = true;
#if HAS_TFT
if (moduleConfig.external_notification.nag_timeout == default_ringtone_nag_secs)
if (moduleConfig.external_notification.nag_timeout == 60)
moduleConfig.external_notification.nag_timeout = 0;
#else
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
moduleConfig.external_notification.nag_timeout = 60;
#endif
#endif
#ifdef NANO_G2_ULTRA
@@ -1319,13 +1301,6 @@ void NodeDB::loadFromDisk()
saveToDisk(SEGMENT_MODULECONFIG);
}
#if ARCH_PORTDUINO
// set any config overrides
if (settingsMap[has_configDisplayMode]) {
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)settingsMap[configDisplayMode];
}
#endif
}
/** Save a protobuf from a file, return true for success */
@@ -1637,6 +1612,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
"to regenerate your public keys.";
LOG_WARN(warning, p.long_name);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->which_payload_variant = meshtastic_ClientNotification_duplicated_public_key_tag;
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->time = getValidTime(RTCQualityFromNet);
sprintf(cn->message, warning, p.long_name);
@@ -1861,18 +1837,36 @@ UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
}
bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest)
bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t keyToTest)
{
if (keyToTest.size == 32) {
uint8_t keyHash[32] = {0};
memcpy(keyHash, keyToTest.bytes, keyToTest.size);
crypto->hash(keyHash, 32);
for (int i = 0; i < sizeof(LOW_ENTROPY_HASHES) / sizeof(LOW_ENTROPY_HASHES[0]); i++) {
if (memcmp(keyHash, LOW_ENTROPY_HASHES[i], sizeof(LOW_ENTROPY_HASHES[0])) == 0) {
if (memcmp(keyHash, LOW_ENTROPY_HASH1, sizeof(LOW_ENTROPY_HASH1)) ==
0 || // should become an array that gets looped through rather than this abomination
memcmp(keyHash, LOW_ENTROPY_HASH2, sizeof(LOW_ENTROPY_HASH2)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH3, sizeof(LOW_ENTROPY_HASH3)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH4, sizeof(LOW_ENTROPY_HASH4)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH5, sizeof(LOW_ENTROPY_HASH5)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH6, sizeof(LOW_ENTROPY_HASH6)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH7, sizeof(LOW_ENTROPY_HASH7)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH8, sizeof(LOW_ENTROPY_HASH8)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH9, sizeof(LOW_ENTROPY_HASH9)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH10, sizeof(LOW_ENTROPY_HASH10)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH11, sizeof(LOW_ENTROPY_HASH11)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH12, sizeof(LOW_ENTROPY_HASH12)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH13, sizeof(LOW_ENTROPY_HASH13)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH14, sizeof(LOW_ENTROPY_HASH14)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH15, sizeof(LOW_ENTROPY_HASH15)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH16, sizeof(LOW_ENTROPY_HASH16)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH17, sizeof(LOW_ENTROPY_HASH17)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH18, sizeof(LOW_ENTROPY_HASH18)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH19, sizeof(LOW_ENTROPY_HASH19)) == 0 ||
memcmp(keyHash, LOW_ENTROPY_HASH20, sizeof(LOW_ENTROPY_HASH20)) == 0) {
return true;
}
}
}
return false;
}

View File

@@ -18,57 +18,68 @@
#endif
#if !defined(MESHTASTIC_EXCLUDE_PKI)
// E3B0C442 is the blank hash
static const uint8_t LOW_ENTROPY_HASHES[][32] = {
{0xf4, 0x7e, 0xcc, 0x17, 0xe6, 0xb4, 0xa3, 0x22, 0xec, 0xee, 0xd9, 0x08, 0x4f, 0x39, 0x63, 0xea,
0x80, 0x75, 0xe1, 0x24, 0xce, 0x05, 0x36, 0x69, 0x63, 0xb2, 0xcb, 0xc0, 0x28, 0xd3, 0x34, 0x8b},
{0x5a, 0x9e, 0xa2, 0xa6, 0x8a, 0xa6, 0x66, 0xc1, 0x5f, 0x55, 0x00, 0x64, 0xa3, 0xa6, 0xfe, 0x71,
0xc0, 0xbb, 0x82, 0xc3, 0x32, 0x3d, 0x7a, 0x7a, 0xe3, 0x6e, 0xfd, 0xdd, 0xad, 0x3a, 0x66, 0xb9},
{0xb3, 0xdf, 0x3b, 0x2e, 0x67, 0xb6, 0xd5, 0xf8, 0xdf, 0x76, 0x2c, 0x45, 0x5e, 0x2e, 0xbd, 0x16,
0xc5, 0xf8, 0x67, 0xaa, 0x15, 0xf8, 0x92, 0x0b, 0xdf, 0x5a, 0x66, 0x50, 0xac, 0x0d, 0xbb, 0x2f},
{0x3b, 0x8f, 0x86, 0x3a, 0x38, 0x1f, 0x77, 0x39, 0xa9, 0x4e, 0xef, 0x91, 0x18, 0x5a, 0x62, 0xe1,
0xaa, 0x9d, 0x36, 0xea, 0xce, 0x60, 0x35, 0x8d, 0x9d, 0x1f, 0xf4, 0xb8, 0xc9, 0x13, 0x6a, 0x5d},
{0x36, 0x7e, 0x2d, 0xe1, 0x84, 0x5f, 0x42, 0x52, 0x29, 0x11, 0x0a, 0x25, 0x64, 0x54, 0x6a, 0x6b,
0xfd, 0xb6, 0x65, 0xff, 0x15, 0x1a, 0x51, 0x71, 0x22, 0x40, 0x57, 0xf6, 0x91, 0x9b, 0x64, 0x58},
{0x16, 0x77, 0xeb, 0xa4, 0x52, 0x91, 0xfb, 0x26, 0xcf, 0x8f, 0xd7, 0xd9, 0xd1, 0x5d, 0xc4, 0x68,
0x73, 0x75, 0xed, 0xc5, 0x95, 0x58, 0xee, 0x90, 0x56, 0xd4, 0x2f, 0x31, 0x29, 0xf7, 0x8c, 0x1f},
{0x31, 0x8c, 0xa9, 0x5e, 0xed, 0x3c, 0x12, 0xbf, 0x97, 0x9c, 0x47, 0x8e, 0x98, 0x9d, 0xc2, 0x3e,
0x86, 0x23, 0x90, 0x29, 0xc8, 0xb0, 0x20, 0xf8, 0xb1, 0xb0, 0xaa, 0x19, 0x2a, 0xcf, 0x0a, 0x54},
{0xa4, 0x8a, 0x99, 0x0e, 0x51, 0xdc, 0x12, 0x20, 0xf3, 0x13, 0xf5, 0x2b, 0x3a, 0xe2, 0x43, 0x42,
0xc6, 0x52, 0x98, 0xcd, 0xbb, 0xca, 0xb1, 0x31, 0xa0, 0xd4, 0xd6, 0x30, 0xf3, 0x27, 0xfb, 0x49},
{0xd2, 0x3f, 0x13, 0x8d, 0x22, 0x04, 0x8d, 0x07, 0x59, 0x58, 0xa0, 0xf9, 0x55, 0xcf, 0x30, 0xa0,
0x2e, 0x2f, 0xca, 0x80, 0x20, 0xe4, 0xde, 0xa1, 0xad, 0xd9, 0x58, 0xb3, 0x43, 0x2b, 0x22, 0x70},
{0x40, 0x41, 0xec, 0x6a, 0xd2, 0xd6, 0x03, 0xe4, 0x9a, 0x9e, 0xbd, 0x6c, 0x0a, 0x9b, 0x75, 0xa4,
0xbc, 0xab, 0x6f, 0xa7, 0x95, 0xff, 0x2d, 0xf6, 0xe9, 0xb9, 0xab, 0x4c, 0x0c, 0x1c, 0xd0, 0x3b},
{0x22, 0x49, 0x32, 0x2b, 0x00, 0xf9, 0x22, 0xfa, 0x17, 0x02, 0xe9, 0x64, 0x82, 0xf0, 0x4d, 0x1b,
0xc7, 0x04, 0xfc, 0xdc, 0x8c, 0x5e, 0xb6, 0xd9, 0x16, 0xd6, 0x37, 0xce, 0x59, 0xaa, 0x09, 0x49},
{0x48, 0x6f, 0x1e, 0x48, 0x97, 0x88, 0x64, 0xac, 0xe8, 0xeb, 0x30, 0xa3, 0xc3, 0xe1, 0xcf, 0x97,
0x39, 0xa6, 0x55, 0x5b, 0x5f, 0xbf, 0x18, 0xb7, 0x3a, 0xdf, 0xa8, 0x75, 0xe7, 0x9d, 0xe0, 0x1e},
{0x09, 0xb4, 0xe2, 0x6d, 0x28, 0x98, 0xc9, 0x47, 0x66, 0x46, 0xbf, 0xff, 0x58, 0x17, 0x91, 0xaa,
0xc3, 0xbf, 0x4a, 0x9d, 0x0b, 0x88, 0xb1, 0xf1, 0x03, 0xdd, 0x61, 0xd7, 0xba, 0x9e, 0x64, 0x98},
{0x39, 0x39, 0x84, 0xe0, 0x22, 0x2f, 0x7d, 0x78, 0x45, 0x18, 0x72, 0xb4, 0x13, 0xd2, 0x01, 0x2f,
0x3c, 0xa1, 0xb0, 0xfe, 0x39, 0xd0, 0xf1, 0x3c, 0x72, 0xd6, 0xef, 0x54, 0xd5, 0x77, 0x22, 0xa0},
{0x0a, 0xda, 0x5f, 0xec, 0xff, 0x5c, 0xc0, 0x2e, 0x5f, 0xc4, 0x8d, 0x03, 0xe5, 0x80, 0x59, 0xd3,
0x5d, 0x49, 0x86, 0xe9, 0x8d, 0xf6, 0xf6, 0x16, 0x35, 0x3d, 0xf9, 0x9b, 0x29, 0x55, 0x9e, 0x64},
{0x08, 0x56, 0xF0, 0xD7, 0xEF, 0x77, 0xD6, 0x11, 0x1C, 0x8F, 0x95, 0x2D, 0x3C, 0xDF, 0xB1, 0x22,
0xBF, 0x60, 0x9B, 0xE5, 0xA9, 0xC0, 0x6E, 0x4B, 0x01, 0xDC, 0xD1, 0x57, 0x44, 0xB2, 0xA5, 0xCF},
{0x2C, 0xB2, 0x77, 0x85, 0xD6, 0xB7, 0x48, 0x9C, 0xFE, 0xBC, 0x80, 0x26, 0x60, 0xF4, 0x6D, 0xCE,
0x11, 0x31, 0xA2, 0x1E, 0x33, 0x0A, 0x6D, 0x2B, 0x00, 0xFA, 0x0C, 0x90, 0x95, 0x8F, 0x5C, 0x6B},
{0xFA, 0x59, 0xC8, 0x6E, 0x94, 0xEE, 0x75, 0xC9, 0x9A, 0xB0, 0xFE, 0x89, 0x36, 0x40, 0xC9, 0x99,
0x4A, 0x3B, 0xF4, 0xAA, 0x12, 0x24, 0xA2, 0x0F, 0xF9, 0xD1, 0x08, 0xCB, 0x78, 0x19, 0xAA, 0xE5},
{0x6E, 0x42, 0x7A, 0x4A, 0x8C, 0x61, 0x62, 0x22, 0xA1, 0x89, 0xD3, 0xA4, 0xC2, 0x19, 0xA3, 0x83,
0x53, 0xA7, 0x7A, 0x0A, 0x89, 0xE2, 0x54, 0x52, 0x62, 0x3D, 0xE7, 0xCA, 0x8C, 0xF6, 0x6A, 0x60},
{0x20, 0x27, 0x2F, 0xBA, 0x0C, 0x99, 0xD7, 0x29, 0xF3, 0x11, 0x35, 0x89, 0x9D, 0x0E, 0x24, 0xA1,
0xC3, 0xCB, 0xDF, 0x8A, 0xF1, 0xC6, 0xFE, 0xD0, 0xD7, 0x9F, 0x92, 0xD6, 0x8F, 0x59, 0xBF, 0xE4},
{0x91, 0x70, 0xb4, 0x7c, 0xfb, 0xff, 0xa0, 0x59, 0x6a, 0x25, 0x1c, 0xa9, 0x9e, 0xe9, 0x43, 0x81,
0x5d, 0x74, 0xb1, 0xb1, 0x09, 0x28, 0x00, 0x4a, 0xaf, 0xe3, 0xfc, 0xa9, 0x4e, 0x27, 0x76, 0x4c},
{0x85, 0xfe, 0x7c, 0xec, 0xb6, 0x78, 0x74, 0xc3, 0xec, 0xe1, 0x32, 0x7f, 0xb0, 0xb7, 0x02, 0x74,
0xf9, 0x23, 0xd8, 0xe7, 0xfa, 0x14, 0xe6, 0xee, 0x66, 0x44, 0xb1, 0x8c, 0xa5, 0x2f, 0x7e, 0xd2},
{0x8e, 0x66, 0x65, 0x7b, 0x3b, 0x6f, 0x7e, 0xcc, 0x57, 0xb4, 0x57, 0xea, 0xcc, 0x83, 0xf5, 0xaa,
0xf7, 0x65, 0xa3, 0xce, 0x93, 0x72, 0x13, 0xc1, 0xb6, 0x46, 0x7b, 0x29, 0x45, 0xb5, 0xc8, 0x93},
{0xcc, 0x11, 0xfb, 0x1a, 0xab, 0xa1, 0x31, 0x87, 0x6a, 0xc6, 0xde, 0x88, 0x87, 0xa9, 0xb9, 0x59,
0x37, 0x82, 0x8d, 0xb2, 0xcc, 0xd8, 0x97, 0x40, 0x9a, 0x5c, 0x8f, 0x40, 0x55, 0xcb, 0x4c, 0x3e}};
static const char LOW_ENTROPY_WARNING[] = "Compromised keys were detected and regenerated.";
static const uint8_t LOW_ENTROPY_HASH1[] = {0xf4, 0x7e, 0xcc, 0x17, 0xe6, 0xb4, 0xa3, 0x22, 0xec, 0xee, 0xd9,
0x08, 0x4f, 0x39, 0x63, 0xea, 0x80, 0x75, 0xe1, 0x24, 0xce, 0x05,
0x36, 0x69, 0x63, 0xb2, 0xcb, 0xc0, 0x28, 0xd3, 0x34, 0x8b};
static const uint8_t LOW_ENTROPY_HASH2[] = {0x5a, 0x9e, 0xa2, 0xa6, 0x8a, 0xa6, 0x66, 0xc1, 0x5f, 0x55, 0x00,
0x64, 0xa3, 0xa6, 0xfe, 0x71, 0xc0, 0xbb, 0x82, 0xc3, 0x32, 0x3d,
0x7a, 0x7a, 0xe3, 0x6e, 0xfd, 0xdd, 0xad, 0x3a, 0x66, 0xb9};
static const uint8_t LOW_ENTROPY_HASH3[] = {0xb3, 0xdf, 0x3b, 0x2e, 0x67, 0xb6, 0xd5, 0xf8, 0xdf, 0x76, 0x2c,
0x45, 0x5e, 0x2e, 0xbd, 0x16, 0xc5, 0xf8, 0x67, 0xaa, 0x15, 0xf8,
0x92, 0x0b, 0xdf, 0x5a, 0x66, 0x50, 0xac, 0x0d, 0xbb, 0x2f};
static const uint8_t LOW_ENTROPY_HASH4[] = {0x3b, 0x8f, 0x86, 0x3a, 0x38, 0x1f, 0x77, 0x39, 0xa9, 0x4e, 0xef,
0x91, 0x18, 0x5a, 0x62, 0xe1, 0xaa, 0x9d, 0x36, 0xea, 0xce, 0x60,
0x35, 0x8d, 0x9d, 0x1f, 0xf4, 0xb8, 0xc9, 0x13, 0x6a, 0x5d};
static const uint8_t LOW_ENTROPY_HASH5[] = {0x36, 0x7e, 0x2d, 0xe1, 0x84, 0x5f, 0x42, 0x52, 0x29, 0x11, 0x0a,
0x25, 0x64, 0x54, 0x6a, 0x6b, 0xfd, 0xb6, 0x65, 0xff, 0x15, 0x1a,
0x51, 0x71, 0x22, 0x40, 0x57, 0xf6, 0x91, 0x9b, 0x64, 0x58};
static const uint8_t LOW_ENTROPY_HASH6[] = {0x16, 0x77, 0xeb, 0xa4, 0x52, 0x91, 0xfb, 0x26, 0xcf, 0x8f, 0xd7,
0xd9, 0xd1, 0x5d, 0xc4, 0x68, 0x73, 0x75, 0xed, 0xc5, 0x95, 0x58,
0xee, 0x90, 0x56, 0xd4, 0x2f, 0x31, 0x29, 0xf7, 0x8c, 0x1f};
static const uint8_t LOW_ENTROPY_HASH7[] = {0x31, 0x8c, 0xa9, 0x5e, 0xed, 0x3c, 0x12, 0xbf, 0x97, 0x9c, 0x47,
0x8e, 0x98, 0x9d, 0xc2, 0x3e, 0x86, 0x23, 0x90, 0x29, 0xc8, 0xb0,
0x20, 0xf8, 0xb1, 0xb0, 0xaa, 0x19, 0x2a, 0xcf, 0x0a, 0x54};
static const uint8_t LOW_ENTROPY_HASH8[] = {0xa4, 0x8a, 0x99, 0x0e, 0x51, 0xdc, 0x12, 0x20, 0xf3, 0x13, 0xf5,
0x2b, 0x3a, 0xe2, 0x43, 0x42, 0xc6, 0x52, 0x98, 0xcd, 0xbb, 0xca,
0xb1, 0x31, 0xa0, 0xd4, 0xd6, 0x30, 0xf3, 0x27, 0xfb, 0x49};
static const uint8_t LOW_ENTROPY_HASH9[] = {0xd2, 0x3f, 0x13, 0x8d, 0x22, 0x04, 0x8d, 0x07, 0x59, 0x58, 0xa0,
0xf9, 0x55, 0xcf, 0x30, 0xa0, 0x2e, 0x2f, 0xca, 0x80, 0x20, 0xe4,
0xde, 0xa1, 0xad, 0xd9, 0x58, 0xb3, 0x43, 0x2b, 0x22, 0x70};
static const uint8_t LOW_ENTROPY_HASH10[] = {0x40, 0x41, 0xec, 0x6a, 0xd2, 0xd6, 0x03, 0xe4, 0x9a, 0x9e, 0xbd,
0x6c, 0x0a, 0x9b, 0x75, 0xa4, 0xbc, 0xab, 0x6f, 0xa7, 0x95, 0xff,
0x2d, 0xf6, 0xe9, 0xb9, 0xab, 0x4c, 0x0c, 0x1c, 0xd0, 0x3b};
static const uint8_t LOW_ENTROPY_HASH11[] = {0x22, 0x49, 0x32, 0x2b, 0x00, 0xf9, 0x22, 0xfa, 0x17, 0x02, 0xe9,
0x64, 0x82, 0xf0, 0x4d, 0x1b, 0xc7, 0x04, 0xfc, 0xdc, 0x8c, 0x5e,
0xb6, 0xd9, 0x16, 0xd6, 0x37, 0xce, 0x59, 0xaa, 0x09, 0x49};
static const uint8_t LOW_ENTROPY_HASH12[] = {0x48, 0x6f, 0x1e, 0x48, 0x97, 0x88, 0x64, 0xac, 0xe8, 0xeb, 0x30,
0xa3, 0xc3, 0xe1, 0xcf, 0x97, 0x39, 0xa6, 0x55, 0x5b, 0x5f, 0xbf,
0x18, 0xb7, 0x3a, 0xdf, 0xa8, 0x75, 0xe7, 0x9d, 0xe0, 0x1e};
static const uint8_t LOW_ENTROPY_HASH13[] = {0x09, 0xb4, 0xe2, 0x6d, 0x28, 0x98, 0xc9, 0x47, 0x66, 0x46, 0xbf,
0xff, 0x58, 0x17, 0x91, 0xaa, 0xc3, 0xbf, 0x4a, 0x9d, 0x0b, 0x88,
0xb1, 0xf1, 0x03, 0xdd, 0x61, 0xd7, 0xba, 0x9e, 0x64, 0x98};
static const uint8_t LOW_ENTROPY_HASH14[] = {0x39, 0x39, 0x84, 0xe0, 0x22, 0x2f, 0x7d, 0x78, 0x45, 0x18, 0x72,
0xb4, 0x13, 0xd2, 0x01, 0x2f, 0x3c, 0xa1, 0xb0, 0xfe, 0x39, 0xd0,
0xf1, 0x3c, 0x72, 0xd6, 0xef, 0x54, 0xd5, 0x77, 0x22, 0xa0};
static const uint8_t LOW_ENTROPY_HASH15[] = {0x0a, 0xda, 0x5f, 0xec, 0xff, 0x5c, 0xc0, 0x2e, 0x5f, 0xc4, 0x8d,
0x03, 0xe5, 0x80, 0x59, 0xd3, 0x5d, 0x49, 0x86, 0xe9, 0x8d, 0xf6,
0xf6, 0x16, 0x35, 0x3d, 0xf9, 0x9b, 0x29, 0x55, 0x9e, 0x64};
static const uint8_t LOW_ENTROPY_HASH16[] = {0x08, 0x56, 0xF0, 0xD7, 0xEF, 0x77, 0xD6, 0x11, 0x1C, 0x8F, 0x95,
0x2D, 0x3C, 0xDF, 0xB1, 0x22, 0xBF, 0x60, 0x9B, 0xE5, 0xA9, 0xC0,
0x6E, 0x4B, 0x01, 0xDC, 0xD1, 0x57, 0x44, 0xB2, 0xA5, 0xCF};
static const uint8_t LOW_ENTROPY_HASH17[] = {0x2C, 0xB2, 0x77, 0x85, 0xD6, 0xB7, 0x48, 0x9C, 0xFE, 0xBC, 0x80,
0x26, 0x60, 0xF4, 0x6D, 0xCE, 0x11, 0x31, 0xA2, 0x1E, 0x33, 0x0A,
0x6D, 0x2B, 0x00, 0xFA, 0x0C, 0x90, 0x95, 0x8F, 0x5C, 0x6B};
static const uint8_t LOW_ENTROPY_HASH18[] = {0xFA, 0x59, 0xC8, 0x6E, 0x94, 0xEE, 0x75, 0xC9, 0x9A, 0xB0, 0xFE,
0x89, 0x36, 0x40, 0xC9, 0x99, 0x4A, 0x3B, 0xF4, 0xAA, 0x12, 0x24,
0xA2, 0x0F, 0xF9, 0xD1, 0x08, 0xCB, 0x78, 0x19, 0xAA, 0xE5};
static const uint8_t LOW_ENTROPY_HASH19[] = {0x6E, 0x42, 0x7A, 0x4A, 0x8C, 0x61, 0x62, 0x22, 0xA1, 0x89, 0xD3,
0xA4, 0xC2, 0x19, 0xA3, 0x83, 0x53, 0xA7, 0x7A, 0x0A, 0x89, 0xE2,
0x54, 0x52, 0x62, 0x3D, 0xE7, 0xCA, 0x8C, 0xF6, 0x6A, 0x60};
static const uint8_t LOW_ENTROPY_HASH20[] = {0x20, 0x27, 0x2F, 0xBA, 0x0C, 0x99, 0xD7, 0x29, 0xF3, 0x11, 0x35,
0x89, 0x9D, 0x0E, 0x24, 0xA1, 0xC3, 0xCB, 0xDF, 0x8A, 0xF1, 0xC6,
0xFE, 0xD0, 0xD7, 0x9F, 0x92, 0xD6, 0x8F, 0x59, 0xBF, 0xE4};
static const char LOW_ENTROPY_WARNING[] = "Compromised keys detected, please regenerate.";
#endif
/*
DeviceState versions used to be defined in the .proto file but really only this function cares. So changed to a
@@ -268,7 +279,7 @@ class NodeDB
bool hasValidPosition(const meshtastic_NodeInfoLite *n);
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t keyToTest);
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,

Some files were not shown because too many files have changed in this diff Show More