Merge branch 'master' into scd4x

This commit is contained in:
Austin 2025-07-21 16:30:32 -04:00 committed by GitHub
commit f0775c586f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
468 changed files with 2937 additions and 1386 deletions

View File

@ -3,6 +3,9 @@ name: Build ESP32
on:
workflow_call:
inputs:
version:
required: true
type: string
board:
required: true
type: string
@ -14,11 +17,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Build ESP32
id: build
@ -33,7 +35,7 @@ jobs:
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-esp32-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
name: firmware-esp32-${{ inputs.board }}-${{ inputs.version }}.zip
overwrite: true
path: |
release/*.bin

View File

@ -3,6 +3,9 @@ name: Build ESP32-C3
on:
workflow_call:
inputs:
version:
required: true
type: string
board:
required: true
type: string
@ -14,11 +17,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Build ESP32-C3
id: build
@ -33,7 +35,7 @@ jobs:
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-esp32c3-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
name: firmware-esp32c3-${{ inputs.board }}-${{ inputs.version }}.zip
overwrite: true
path: |
release/*.bin

View File

@ -3,6 +3,9 @@ name: Build ESP32-C6
on:
workflow_call:
inputs:
version:
required: true
type: string
board:
required: true
type: string
@ -14,11 +17,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Build ESP32-C6
id: build
@ -33,7 +35,7 @@ jobs:
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-esp32c6-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
name: firmware-esp32c6-${{ inputs.board }}-${{ inputs.version }}.zip
overwrite: true
path: |
release/*.bin

View File

@ -3,6 +3,9 @@ name: Build ESP32-S3
on:
workflow_call:
inputs:
version:
required: true
type: string
board:
required: true
type: string
@ -14,11 +17,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Build ESP32-S3
id: build
@ -33,7 +35,7 @@ jobs:
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-esp32s3-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
name: firmware-esp32s3-${{ inputs.board }}-${{ inputs.version }}.zip
overwrite: true
path: |
release/*.bin

View File

@ -3,6 +3,9 @@ name: Build NRF52
on:
workflow_call:
inputs:
version:
required: true
type: string
board:
required: true
type: string
@ -14,11 +17,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Build NRF52
id: build
@ -31,7 +33,7 @@ jobs:
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-nrf52840-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
name: firmware-nrf52840-${{ inputs.board }}-${{ inputs.version }}.zip
overwrite: true
path: |
release/*.uf2

View File

@ -3,6 +3,9 @@ name: Build RPI2040
on:
workflow_call:
inputs:
version:
required: true
type: string
board:
required: true
type: string
@ -14,11 +17,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Build Raspberry Pi 2040
id: build
@ -31,7 +33,7 @@ jobs:
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-rp2040-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
name: firmware-rp2040-${{ inputs.board }}-${{ inputs.version }}.zip
overwrite: true
path: |
release/*.uf2

View File

@ -3,6 +3,9 @@ name: Build STM32
on:
workflow_call:
inputs:
version:
required: true
type: string
board:
required: true
type: string
@ -14,11 +17,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Build STM32WL
id: build
@ -31,7 +33,7 @@ jobs:
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-stm32-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
name: firmware-stm32-${{ inputs.board }}-${{ inputs.version }}.zip
overwrite: true
path: |
release/*.hex

View File

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

View File

@ -33,9 +33,7 @@ jobs:
arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32, check]
runs-on: ubuntu-latest
steps:
- id: checkout
uses: actions/checkout@v4
name: Checkout base
- uses: actions/checkout@v4
- id: jsonStep
run: |
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
@ -55,6 +53,21 @@ jobs:
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:
@ -72,66 +85,73 @@ jobs:
run: bin/check-all.sh ${{ matrix.board }}
build-esp32:
needs: setup
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
uses: ./.github/workflows/build_esp32.yml
with:
version: ${{ needs.version.outputs.long }}
board: ${{ matrix.board }}
build-esp32-s3:
needs: setup
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_esp32_s3.yml
with:
version: ${{ needs.version.outputs.long }}
board: ${{ matrix.board }}
build-esp32-c3:
needs: setup
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_esp32_c3.yml
with:
version: ${{ needs.version.outputs.long }}
board: ${{ matrix.board }}
build-esp32-c6:
needs: setup
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
uses: ./.github/workflows/build_esp32_c6.yml
with:
version: ${{ needs.version.outputs.long }}
board: ${{ matrix.board }}
build-nrf52:
needs: setup
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_nrf52.yml
with:
version: ${{ needs.version.outputs.long }}
board: ${{ matrix.board }}
build-rpi2040:
needs: setup
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_rpi2040.yml
with:
version: ${{ needs.version.outputs.long }}
board: ${{ matrix.board }}
build-stm32:
needs: setup
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
uses: ./.github/workflows/build_stm32.yml
with:
version: ${{ needs.version.outputs.long }}
board: ${{ matrix.board }}
build-debian-src:
@ -214,6 +234,7 @@ jobs:
runs-on: ubuntu-latest
needs:
[
version,
build-esp32,
build-esp32-s3,
build-esp32-c3,
@ -238,17 +259,13 @@ 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}}-${{ steps.version.outputs.long }}
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
path: |
./firmware-*.bin
@ -264,7 +281,7 @@ jobs:
- uses: actions/download-artifact@v4
with:
name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
@ -278,12 +295,12 @@ jobs:
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./output
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v4
with:
name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
overwrite: true
path: ./*.elf
retention-days: 30
@ -291,8 +308,8 @@ jobs:
- uses: scruplelesswizard/comment-artifact@main
if: ${{ github.event_name == 'pull_request' }}
with:
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"
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"
github-token: ${{ secrets.GITHUB_TOKEN }}
release-artifacts:
@ -301,6 +318,7 @@ jobs:
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
- version
- gather-artifacts
- build-debian-src
- package-pio-deps-native-tft
@ -313,44 +331,36 @@ 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 ${{ steps.version.outputs.long }} Alpha
tag_name: v${{ steps.version.outputs.long }}
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.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-${{ steps.version.outputs.deb }}~UNRELEASED-src
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v4
with:
pattern: platformio-deps-native-tft-${{ steps.version.outputs.long }}
pattern: platformio-deps-native-tft-${{ needs.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-${{ steps.version.outputs.deb }}-src.zip ./debian-src
zip -9 -r ./platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip ./pio-deps-native-tft
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
# For diagnostics
- name: Display structure of downloaded files
@ -360,8 +370,8 @@ jobs:
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
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
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -372,7 +382,7 @@ jobs:
arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32]
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-artifacts]
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v4
@ -382,13 +392,9 @@ 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}}-${{ steps.version.outputs.long }}
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
@ -401,16 +407,16 @@ jobs:
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./output
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v4
with:
name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
merge-multiple: true
path: ./elfs
- name: Zip debug elfs
run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./elfs
run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
# For diagnostics
- name: Display structure of downloaded files
@ -420,15 +426,15 @@ jobs:
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
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
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-firmware:
runs-on: ubuntu-24.04
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-firmware]
needs: [release-firmware, version]
env:
targets: esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,stm32
steps:
@ -440,13 +446,9 @@ 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 }}}-${{ steps.version.outputs.long }}
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
@ -460,9 +462,9 @@ jobs:
external_repository: meshtastic/meshtastic.github.io
publish_branch: master
publish_dir: ./publish
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ steps.version.outputs.long }}
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
keep_files: true
user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com
commit_message: ${{ steps.version.outputs.long }}
commit_message: ${{ needs.version.outputs.long }}
enable_jekyll: true

24
.github/workflows/pr_enforce_labels.yml vendored Normal file
View File

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

View File

@ -34,7 +34,9 @@ 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.450
- renovate@41.29.1
- checkov@3.2.451
- renovate@41.38.1
- prettier@3.6.2
- trufflehog@3.89.2
- trufflehog@3.90.0
- yamllint@1.37.1
- bandit@1.8.6
- trivy@0.64.1
- taplo@0.9.3
- ruff@0.12.2
- ruff@0.12.4
- isort@6.0.1
- markdownlint@0.45.0
- oxipng@9.1.5

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=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.0
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
https://github.com/lewisxhe/XPowersLib/archive/v0.3.0.zip
# 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

@ -39,7 +39,7 @@ build_flags =
-Isrc/platform/portduino
-DRADIOLIB_EEPROM_UNSUPPORTED
-DPORTDUINO_LINUX_HARDWARE
-DHAS_UDP_MULTICAST
-DHAS_UDP_MULTICAST=1
-lpthread
-lstdc++fs
-lbluetooth

View File

@ -23,14 +23,20 @@ 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
-DDEBUG_MUTE ; You can #undef DEBUG_MUTE in certain source files if you need the logs.
-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>

View File

@ -87,6 +87,9 @@
</screenshots>
<releases>
<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>

View File

@ -3,6 +3,7 @@
# trunk-ignore-all(flake8/F821): For SConstruct imports
import sys
from os.path import join
import subprocess
import json
import re
@ -92,6 +93,17 @@ 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)
@ -117,6 +129,7 @@ flags = [
"-DAPP_VERSION=" + verObj["long"],
"-DAPP_VERSION_SHORT=" + verObj["short"],
"-DAPP_ENV=" + env.get("PIOENV"),
"-DAPP_REPO=" + repo_owner,
] + pref_flags
print ("Using flags:")

43
boards/t-deck-pro.json Normal file
View File

@ -0,0 +1,43 @@
{
"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"
}

7
debian/changelog vendored
View File

@ -1,4 +1,4 @@
meshtasticd (2.7.3.0) UNRELEASED; urgency=medium
meshtasticd (2.7.4.0) UNRELEASED; urgency=medium
[ Austin Lane ]
* Initial packaging
@ -31,4 +31,7 @@ meshtasticd (2.7.3.0) UNRELEASED; urgency=medium
[ Ubuntu ]
* GitHub Actions Automatic version bump
-- Ubuntu <github-actions[bot]@users.noreply.github.com> Thu, 10 Jul 2025 16:29:27 +0000
[ ]
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Sat, 19 Jul 2025 11:36:55 +0000

View File

@ -6,7 +6,9 @@ default_envs = tbeam
extra_configs =
arch/*/*.ini
variants/*/platformio.ini
variants/*/platformio.ini ; Remove when all variants migrated to new dir structure
variants/*/*/platformio.ini
variants/*/diy/*/platformio.ini
src/graphics/niche/InkHUD/PlatformioConfig.ini
description = Meshtastic

@ -1 +1 @@
Subproject commit 584f0a3a359103acf0bfce506c1b1fc32c639841
Subproject commit fa02e14d8d01850336eaea0e9552aef4f08f0a40

View File

@ -120,6 +120,15 @@ 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
@ -665,6 +674,8 @@ bool Power::setup()
found = true;
} else if (lipoInit()) {
found = true;
} else if (lipoChargerInit()) {
found = true;
} else if (analogInit()) {
found = true;
}
@ -681,7 +692,16 @@ bool Power::setup()
void Power::shutdown()
{
LOG_INFO("Shutting Down");
#if HAS_SCREEN
if (screen) {
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
}
#endif
#if !defined(ARCH_STM32WL)
playShutdownMelody();
#endif
nodeDB->saveToDisk();
#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
#ifdef PIN_LED1
@ -693,7 +713,11 @@ void Power::shutdown()
#ifdef PIN_LED3
ledOff(PIN_LED3);
#endif
doDeepSleep(DELAY_FOREVER, false, false);
doDeepSleep(DELAY_FOREVER, false, true);
#elif defined(ARCH_PORTDUINO)
exit(EXIT_SUCCESS);
#else
LOG_WARN("FIXME implement shutdown for this platform");
#endif
}
@ -1237,3 +1261,144 @@ 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

@ -47,10 +47,6 @@ 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) {
@ -69,10 +65,7 @@ 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
}
needsUpdate = false;
// Run every 100ms when active, less frequently when idle
return needsUpdate ? 100 : 1000;

View File

@ -150,11 +150,12 @@ 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)
// I2C Keyboards (M5Stack, RAK14004, T-Deck, T-Deck Pro, T-Lora Pager, CardKB, BBQ10, MPR121, TCA8418)
#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
@ -193,8 +194,11 @@ 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
#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418_KB
#define PCT2075_ADDR 0x37
#define BQ27220_ADDR 0x55 // same address as TDECK_KB
#define BQ25896_ADDR 0x6B
#define LTR553ALS_ADDR 0x23
// -----------------------------------------------------------------------------
// ACCELEROMETER
@ -208,6 +212,7 @@ 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
// -----------------------------------------------------------------------------
@ -230,6 +235,7 @@ 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,7 +74,12 @@ class ScanI2C
RAK12035,
TCA8418KB,
PCT2075,
BMM150,
CST328,
BQ25896,
BQ27220,
LTR553ALS,
BHI260AP,
BMM150
} DeviceType;
// typedef uint8_t DeviceAddress;

View File

@ -206,7 +206,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
SCAN_SIMPLE_CASE(TDECK_KB_ADDR, TDECKKB, "T-Deck keyboard", (uint8_t)addr.address);
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(BBQ10_KB_ADDR, BBQ10KB, "BB Q10", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address);
@ -396,6 +406,12 @@ 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;
@ -447,6 +463,9 @@ 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,9 @@ 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)
#if defined(RAK2560)
HardwareSerial *GPS::_serial_gps = &Serial2;
#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;
#else
HardwareSerial *GPS::_serial_gps = &Serial1;
#endif

View File

@ -226,7 +226,14 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
time_t res = gm_mktime(&t);
struct timeval tv;
tv.tv_sec = res;
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
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) {

View File

@ -206,7 +206,7 @@ bool EInkDisplay::connect()
adafruitDisplay->setRotation(0);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
}
#elif defined(M5_COREINK)
#elif defined(M5_COREINK) || defined(T_DECK_PRO)
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));

View File

@ -386,9 +386,7 @@ 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
@ -400,10 +398,7 @@ 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();
}
ui->init();
#endif
#ifdef USE_ST7789
pinMode(VTFT_CTRL, OUTPUT);
@ -584,7 +579,7 @@ void Screen::setup()
touchScreenImpl1->init();
}
}
#elif HAS_TOUCHSCREEN
#elif HAS_TOUCHSCREEN && !defined(USE_EINK)
touchScreenImpl1 =
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
touchScreenImpl1->init();
@ -869,6 +864,8 @@ 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;
@ -1004,7 +1001,7 @@ void Screen::setFrames(FrameFocus focus)
// Insert favorite frames *after* collecting them all
if (!favoriteFrames.empty()) {
fsi.positions.firstFavorite = numframes;
for (auto &f : favoriteFrames) {
for (const auto &f : favoriteFrames) {
normalFrames[numframes++] = f;
indicatorIcons.push_back(icon_node);
}
@ -1379,9 +1376,12 @@ 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 &&
devicestate.rx_text_message.from) {
menuHandler::messageResponseMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
if (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) {

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 - 1;
int iconRightEdge = timeX - 2;
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,7 +218,6 @@ 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);
@ -230,6 +229,8 @@ 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) {
@ -285,6 +286,9 @@ 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,
@ -362,7 +366,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// hour hand radius and y coordinate
int16_t hourHandRadius = radius * 0.35;
if (isHighResolution) {
int16_t hourHandRadius = radius * 0.55;
hourHandRadius = radius * 0.55;
}
int16_t hourHandNoonY = centerY - hourHandRadius;
@ -381,7 +385,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
bool isPM = hour >= 12;
if (config.display.use_12h_clock) {
bool isPM = hour >= 12;
isPM = hour >= 12;
display->setFont(FONT_SMALL);
int yOffset = isHighResolution ? 1 : 0;
#ifdef USE_EINK

View File

@ -483,7 +483,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
}
// ****************************
// * Memory Screen *
// * System Screen *
// ****************************
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@ -593,7 +593,19 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
}
line += 1;
char appversionstr[35];
snprintf(appversionstr, sizeof(appversionstr), "Ver.: %s", optstr(APP_VERSION));
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';
}
int textWidth = display->getStringWidth(appversionstr);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], appversionstr);

View File

@ -344,14 +344,37 @@ void menuHandler::homeBaseMenu()
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);
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::systemBaseMenu()
{
// 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
enum optionsNumbers { Back, Notifications, ScreenOptions, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
@ -421,11 +444,11 @@ void menuHandler::favoriteBaseMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
if (selected == Preset) {
cannedMessageModule->LaunchWithDestination(graphics::UIRenderer::currentFavoriteNodeNum);
} else if (selected == 2 && kb_found) {
} else if (selected == Freetext) {
cannedMessageModule->LaunchFreetextWithDestination(graphics::UIRenderer::currentFavoriteNodeNum);
} else if ((!kb_found && selected == 2) || (selected == 3 && kb_found)) {
} else if (selected == Remove) {
menuHandler::menuQueue = menuHandler::remove_favorite;
screen->runNow();
}
@ -678,6 +701,7 @@ 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;
@ -729,7 +753,6 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
screen->runNow();
}
#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);
@ -792,9 +815,8 @@ void menuHandler::shutdownMenu()
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
IF_SCREEN(screen->showSimpleBanner("Shutting Down...", 0));
nodeDB->saveToDisk();
power->shutdown();
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else {
menuQueue = power_menu;
screen->runNow();
@ -827,8 +849,9 @@ 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_PRESERVE);
screen->setFrames(graphics::Screen::FOCUS_DEFAULT);
}
};
screen->showOverlayBanner(bannerOptions);
@ -924,23 +947,28 @@ 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) || HAS_TFT
#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 && !HAS_TFT) {
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) || HAS_TFT
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = ScreenColor;
#endif

View File

@ -48,6 +48,7 @@ class menuHandler
static void ClockFacePicker();
static void messageResponseMenu();
static void homeBaseMenu();
static void textMessageBaseMenu();
static void systemBaseMenu();
static void favoriteBaseMenu();
static void positionBaseMenu();

View File

@ -156,7 +156,7 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
resetBanner();
return;
}
if (curSelected == numDigits) {
if (curSelected == static_cast<int8_t>(numDigits)) {
alertBannerCallback(currentNumber);
resetBanner();
return;

View File

@ -24,6 +24,23 @@ 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
@ -201,27 +218,7 @@ 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;
@ -657,7 +654,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(longName) + display->getStringWidth(shortnameble)) > 10) {
if (SCREEN_WIDTH - (display->getStringWidth(combinedName)) > 10) {
size_t len = strlen(combinedName);
if (len >= 3 && strcmp(combinedName + len - 3, " ()") == 0) {
combinedName[len - 3] = '\0'; // Remove the last three characters
@ -668,7 +665,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(longName);
textWidth = display->getStringWidth(longNameStr.c_str());
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], longNameStr.c_str());

View File

@ -61,6 +61,8 @@ 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,7 +1,6 @@
[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,23 +53,21 @@ bool ButtonThread::initButton(const ButtonConfig &config)
},
this);
if (config.longPress != INPUT_BROKER_NONE) {
_longPress = config.longPress;
userButton.attachLongPressStart(
[](void *callerThread) -> void {
ButtonThread *thread = (ButtonThread *)callerThread;
// if (millis() > 30000) // hold off 30s after boot
thread->btnEvent = BUTTON_EVENT_LONG_PRESSED;
},
this);
userButton.attachLongPressStop(
[](void *callerThread) -> void {
ButtonThread *thread = (ButtonThread *)callerThread;
// if (millis() > 30000) // hold off 30s after boot
thread->btnEvent = BUTTON_EVENT_LONG_RELEASED;
},
this);
}
_longPress = config.longPress;
userButton.attachLongPressStart(
[](void *callerThread) -> void {
ButtonThread *thread = (ButtonThread *)callerThread;
// if (millis() > 30000) // hold off 30s after boot
thread->btnEvent = BUTTON_EVENT_LONG_PRESSED;
},
this);
userButton.attachLongPressStop(
[](void *callerThread) -> void {
ButtonThread *thread = (ButtonThread *)callerThread;
// if (millis() > 30000) // hold off 30s after boot
thread->btnEvent = BUTTON_EVENT_LONG_RELEASED;
},
this);
if (config.doublePress != INPUT_BROKER_NONE) {
_doublePress = config.doublePress;
@ -202,11 +200,11 @@ int32_t ButtonThread::runOnce()
break;
}
// Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event)
evt.inputEvent = _longPress;
this->notifyObservers(&evt);
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;
@ -253,7 +251,7 @@ int32_t ButtonThread::runOnce()
// may wake the board immediatedly.
case BUTTON_EVENT_LONG_RELEASED: {
LOG_INFO("LONG PRESS RELEASE");
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
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 = 5000;
uint16_t longLongPressTime = 3900;
input_broker_event triplePress = INPUT_BROKER_NONE;
input_broker_event shortLong = INPUT_BROKER_NONE;
bool touchQuirk = false;
// Constructor to set required parameter
ButtonConfig(uint8_t pin = 0) : pinNumber(pin) {}
explicit 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,
};
ButtonThread(const char *name);
explicit ButtonThread(const char *name);
int32_t runOnce() override;
OneButton userButton;
void attachButtonInterrupts();

View File

@ -233,14 +233,7 @@ void ExpressLRSFiveWay::sendAdhocPing()
// Contained as one method for easier remapping of buttons by user
void ExpressLRSFiveWay::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;
sendKey(INPUT_BROKER_SHUTDOWN);
}
void ExpressLRSFiveWay::click()

View File

@ -1,116 +1,18 @@
// 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
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
#define _TCA8418_LONG_PRESS_THRESHOLD 2000
#define _TCA8418_MULTI_TAP_THRESHOLD 750
unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = {
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] = {
{'1', '.', ',', '?', '!', ':', ';', '-', '_', '\\', '/', '(', ')'}, // 1
{'2', 'a', 'b', 'c', 'A', 'B', 'C'}, // 2
{'3', 'd', 'e', 'f', 'D', 'E', 'F'}, // 3
@ -125,176 +27,35 @@ unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = {
{'#', '@'}, // #
};
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, // #
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, // #
};
#define _TCA8418_LONG_PRESS_THRESHOLD 2000
#define _TCA8418_MULTI_TAP_THRESHOLD 750
TCA8418Keyboard::TCA8418Keyboard() : m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr)
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)
{
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()
{
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);
TCA8418KeyboardBase::reset();
// 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);
// 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();
}
writeRegister(TCA8418_REG_GPIO_DAT_OUT_3, 0x00);
}
void TCA8418Keyboard::pressed(uint8_t key)
@ -354,7 +115,7 @@ void TCA8418Keyboard::released()
int32_t held_interval = now - last_tap;
last_tap = now;
if (tap_interval < _TCA8418_MULTI_TAP_THRESHOLD && should_backspace) {
queueEvent(_TCA8418_BSP);
queueEvent(BSP);
}
if (held_interval > _TCA8418_LONG_PRESS_THRESHOLD) {
queueEvent(TCA8418LongPressMap[last_key]);
@ -366,195 +127,11 @@ 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,82 +1,23 @@
// Based on the MPR121 Keyboard and Adafruit TCA8418 library
#include "configuration.h"
#include <Wire.h>
#include "TCA8418KeyboardBase.h"
#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
/**
* @brief 3x4 keypad with 3 columns and 4 rows
*/
class TCA8418Keyboard : public TCA8418KeyboardBase
{
public:
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
TCA8418Keyboard();
void reset(void) override;
void setBacklight(bool on) override;
enum KeyState { Init = 0, Idle, Held, Busy };
protected:
void pressed(uint8_t key) override;
void released(void) override;
KeyState state;
int8_t last_key;
bool should_backspace;
int8_t next_key;
uint32_t last_tap;
uint8_t char_idx;
int32_t tap_interval;
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;
bool should_backspace;
};

View File

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

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

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

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

@ -0,0 +1,12 @@
#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,4 +67,5 @@ void CardKbI2cImpl::init()
}
#endif
inputBroker->registerSource(this);
kb_found = true;
}

View File

@ -3,10 +3,26 @@
#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)
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
{
this->_originName = name;
}
@ -43,8 +59,8 @@ int32_t KbI2cBase::runOnce()
if (cardkb_found.address == MPR121_KB_ADDR) {
MPRkeyboard.begin(MPR121_KB_ADDR, &Wire1);
}
if (cardkb_found.address == XPOWERS_AXP192_AXP2101_ADDRESS) {
TCAKeyboard.begin(XPOWERS_AXP192_AXP2101_ADDRESS, &Wire1);
if (cardkb_found.address == TCA8418_KB_ADDR) {
TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire1);
}
break;
#endif
@ -58,8 +74,8 @@ int32_t KbI2cBase::runOnce()
if (cardkb_found.address == MPR121_KB_ADDR) {
MPRkeyboard.begin(MPR121_KB_ADDR, &Wire);
}
if (cardkb_found.address == XPOWERS_AXP192_AXP2101_ADDRESS) {
TCAKeyboard.begin(XPOWERS_AXP192_AXP2101_ADDRESS, &Wire);
if (cardkb_found.address == TCA8418_KB_ADDR) {
TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire);
}
break;
case ScanI2C::NO_I2C:
@ -241,41 +257,65 @@ int32_t KbI2cBase::runOnce()
e.kbchar = 0x00;
e.source = this->_originName;
switch (nextEvent) {
case _TCA8418_NONE:
case TCA8418KeyboardBase::NONE:
e.inputEvent = INPUT_BROKER_NONE;
e.kbchar = 0x00;
break;
case _TCA8418_REBOOT:
case TCA8418KeyboardBase::REBOOT:
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_MSG_REBOOT;
break;
case _TCA8418_LEFT:
case TCA8418KeyboardBase::LEFT:
e.inputEvent = INPUT_BROKER_LEFT;
e.kbchar = 0x00;
break;
case _TCA8418_UP:
case TCA8418KeyboardBase::UP:
e.inputEvent = INPUT_BROKER_UP;
e.kbchar = 0x00;
break;
case _TCA8418_DOWN:
case TCA8418KeyboardBase::DOWN:
e.inputEvent = INPUT_BROKER_DOWN;
e.kbchar = 0x00;
break;
case _TCA8418_RIGHT:
case TCA8418KeyboardBase::RIGHT:
e.inputEvent = INPUT_BROKER_RIGHT;
e.kbchar = 0x00;
break;
case _TCA8418_BSP:
case TCA8418KeyboardBase::BSP:
e.inputEvent = INPUT_BROKER_BACK;
e.kbchar = 0x08;
break;
case _TCA8418_SELECT:
case TCA8418KeyboardBase::SELECT:
e.inputEvent = INPUT_BROKER_SELECT;
e.kbchar = 0x00;
break;
case _TCA8418_ESC:
case TCA8418KeyboardBase::ESC:
e.inputEvent = INPUT_BROKER_CANCEL;
e.kbchar = 0;
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;
break;
default:
if (nextEvent > 127) {
@ -291,6 +331,7 @@ int32_t KbI2cBase::runOnce()
LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar);
this->notifyObservers(&e);
}
TCAKeyboard.trigger();
}
break;
}

View File

@ -3,10 +3,11 @@
#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:
@ -22,6 +23,6 @@ class KbI2cBase : public Observable<const InputEvent *>, public concurrency::OST
BBQ10Keyboard Q10keyboard;
MPR121Keyboard MPRkeyboard;
TCA8418Keyboard TCAKeyboard;
TCA8418KeyboardBase &TCAKeyboard;
bool is_sym = false;
};

View File

@ -286,7 +286,7 @@ void lateInitVariant() {}
*/
void printInfo()
{
LOG_INFO("S:B:%d,%s", HW_VENDOR, optstr(APP_VERSION));
LOG_INFO("S:B:%d,%s,%s,%s", HW_VENDOR, optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
}
#ifndef PIO_UNIT_TESTING
void setup()
@ -335,6 +335,15 @@ 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;
@ -515,25 +524,11 @@ void setup()
LOG_INFO("Scan for i2c devices");
#endif
#if 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)
#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2))
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1);
#endif
#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);
#if defined(I2C_SDA)
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
#elif defined(ARCH_PORTDUINO)
if (settingsStrings[i2cdev] != "") {
@ -1056,8 +1051,9 @@ void setup()
mainDelay.interruptFromISR(&higherWake);
};
userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS;
userConfigNoScreen.longPress = INPUT_BROKER_SHUTDOWN;
userConfigNoScreen.longPressTime = 5000;
userConfigNoScreen.longPress = INPUT_BROKER_NONE;
userConfigNoScreen.longPressTime = 500;
userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN;
userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING;
userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE;
UserButtonThread->initButton(userConfigNoScreen);

View File

@ -10,6 +10,10 @@
#include "memGet.h"
#include "configuration.h"
#ifdef ARCH_STM32WL
#include <malloc.h>
#endif
MemGet memGet;
/**
@ -24,6 +28,9 @@ 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;
@ -42,6 +49,9 @@ 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

@ -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,6 +16,7 @@
#include "meshUtils.h"
#include "modules/NodeInfoModule.h"
#include "modules/PositionModule.h"
#include "modules/RoutingModule.h"
#include "power.h"
#include <assert.h>
#include <string>
@ -333,6 +334,21 @@ 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

@ -148,6 +148,9 @@ class MeshService
/// Send a ClientNotification to the phone
void sendClientNotification(meshtastic_ClientNotification *cn);
/// Send an error response to the phone
void sendRoutingErrorResponse(meshtastic_Routing_Error error, const meshtastic_MeshPacket *mp);
bool isToPhoneQueueEmpty();
ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id);

View File

@ -735,7 +735,6 @@ 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);
@ -796,6 +795,13 @@ void NodeDB::installDefaultModuleConfig()
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;
#endif
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312)
// Default to RAK led pin 2 (blue)
moduleConfig.external_notification.enabled = true;
@ -1868,7 +1874,7 @@ 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};

View File

@ -279,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,

View File

@ -181,7 +181,7 @@ PacketHistory::PacketRecord *PacketHistory::find(NodeNum sender, PacketId id)
}
/** Insert/Replace oldest PacketRecord in recentPackets. */
void PacketHistory::insert(PacketRecord &r)
void PacketHistory::insert(const PacketRecord &r)
{
uint32_t now_millis = millis(); // Should not jump with time changes
uint32_t OldtrxTimeMsec = 0;
@ -308,7 +308,7 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const N
return false;
}
PacketRecord *found = find(sender, id);
const PacketRecord *found = find(sender, id);
if (found == NULL) {
#if VERBOSE_PACKET_HISTORY
@ -327,7 +327,7 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const N
/* Check if a certain node was a relayer of a packet in the history given iterator
* @return true if node was indeed a relayer, false if not */
bool PacketHistory::wasRelayer(const uint8_t relayer, PacketRecord &r)
bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r)
{
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
if (r.relayed_by[i] == relayer) {

View File

@ -31,11 +31,11 @@ class PacketHistory
/** Insert/Replace oldest PacketRecord in mx_recentPackets.
* @param r PacketRecord to insert or replace */
void insert(PacketRecord &r); // Insert or replace a packet record in the history
void insert(const PacketRecord &r); // Insert or replace a packet record in the history
/* Check if a certain node was a relayer of a packet in the history given iterator
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, PacketRecord &r);
bool wasRelayer(const uint8_t relayer, const PacketRecord &r);
PacketHistory(const PacketHistory &); // non construction-copyable
PacketHistory &operator=(const PacketHistory &); // non copyable

View File

@ -686,7 +686,8 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
LOG_WARN("Rate limit portnum %d", p.decoded.portnum);
meshtastic_QueueStatus qs = router->getQueueStatus();
service->sendQueueStatusToPhone(qs, 0, p.id);
sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Text messages can only be sent once every 2 seconds");
service->sendRoutingErrorResponse(meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED, &p);
// sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Text messages can only be sent once every 2 seconds");
return false;
}
lastPortNumToRadio[p.decoded.portnum] = millis();

View File

@ -68,6 +68,11 @@ static int32_t reconnectETH()
initApiServer();
}
#endif
#if HAS_UDP_MULTICAST
if (udpHandler && config.network.enabled_protocols & meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST) {
udpHandler->start();
}
#endif
ethStartupComplete = true;
}

View File

@ -2,7 +2,6 @@
#include "configuration.h"
#include <Arduino.h>
#include <functional>
bool initEthernet();
bool isEthernetAvailable();

View File

@ -7,9 +7,9 @@
#include "meshtastic/channel.pb.h"
#include "meshtastic/config.pb.h"
#include "meshtastic/connection_status.pb.h"
#include "meshtastic/device_ui.pb.h"
#include "meshtastic/mesh.pb.h"
#include "meshtastic/module_config.pb.h"
#include "meshtastic/device_ui.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.

View File

@ -102,7 +102,11 @@ typedef enum _meshtastic_Config_DeviceConfig_BuzzerMode {
meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY = 2,
/* Non-notification system buzzer tones only.
Buzzer is enabled only for non-notification tones such as button presses, startup, shutdown, but not for alerts. */
meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY = 3
meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY = 3,
/* Direct Message notifications only.
Buzzer is enabled only for direct messages and alerts, but not for button presses.
External notification config determines the specifics of the notification behavior. */
meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY = 4
} meshtastic_Config_DeviceConfig_BuzzerMode;
/* Bit field of boolean configuration options, indicating which optional
@ -289,7 +293,11 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode {
/* Kazakhstan 433MHz */
meshtastic_Config_LoRaConfig_RegionCode_KZ_433 = 23,
/* Kazakhstan 863MHz */
meshtastic_Config_LoRaConfig_RegionCode_KZ_863 = 24
meshtastic_Config_LoRaConfig_RegionCode_KZ_863 = 24,
/* Nepal 865MHz */
meshtastic_Config_LoRaConfig_RegionCode_NP_865 = 25,
/* Brazil 902MHz */
meshtastic_Config_LoRaConfig_RegionCode_BR_902 = 26
} meshtastic_Config_LoRaConfig_RegionCode;
/* Standard predefined channel settings
@ -476,7 +484,8 @@ typedef struct _meshtastic_Config_DisplayConfig {
/* Number of seconds the screen stays on after pressing the user button or receiving a message
0 for default of one minute MAXUINT for always on */
uint32_t screen_on_secs;
/* How the GPS coordinates are formatted on the OLED screen. */
/* Deprecated in 2.7.4: Unused
How the GPS coordinates are formatted on the OLED screen. */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat gps_format;
/* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds.
Potentially useful for devices without user buttons. */
@ -645,8 +654,8 @@ extern "C" {
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_ARRAYSIZE ((meshtastic_Config_DeviceConfig_RebroadcastMode)(meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY+1))
#define _meshtastic_Config_DeviceConfig_BuzzerMode_MIN meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED
#define _meshtastic_Config_DeviceConfig_BuzzerMode_MAX meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY
#define _meshtastic_Config_DeviceConfig_BuzzerMode_ARRAYSIZE ((meshtastic_Config_DeviceConfig_BuzzerMode)(meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY+1))
#define _meshtastic_Config_DeviceConfig_BuzzerMode_MAX meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY
#define _meshtastic_Config_DeviceConfig_BuzzerMode_ARRAYSIZE ((meshtastic_Config_DeviceConfig_BuzzerMode)(meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY+1))
#define _meshtastic_Config_PositionConfig_PositionFlags_MIN meshtastic_Config_PositionConfig_PositionFlags_UNSET
#define _meshtastic_Config_PositionConfig_PositionFlags_MAX meshtastic_Config_PositionConfig_PositionFlags_SPEED
@ -685,8 +694,8 @@ extern "C" {
#define _meshtastic_Config_DisplayConfig_CompassOrientation_ARRAYSIZE ((meshtastic_Config_DisplayConfig_CompassOrientation)(meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED+1))
#define _meshtastic_Config_LoRaConfig_RegionCode_MIN meshtastic_Config_LoRaConfig_RegionCode_UNSET
#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_KZ_863
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_KZ_863+1))
#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_BR_902
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_BR_902+1))
#define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO

View File

@ -6,10 +6,10 @@
#include <pb.h>
#include <vector>
#include "meshtastic/channel.pb.h"
#include "meshtastic/mesh.pb.h"
#include "meshtastic/telemetry.pb.h"
#include "meshtastic/config.pb.h"
#include "meshtastic/localonly.pb.h"
#include "meshtastic/mesh.pb.h"
#include "meshtastic/telemetry.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.

View File

@ -6,11 +6,11 @@
#include <pb.h>
#include "meshtastic/channel.pb.h"
#include "meshtastic/config.pb.h"
#include "meshtastic/device_ui.pb.h"
#include "meshtastic/module_config.pb.h"
#include "meshtastic/portnums.pb.h"
#include "meshtastic/telemetry.pb.h"
#include "meshtastic/xmodem.pb.h"
#include "meshtastic/device_ui.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
@ -247,32 +247,26 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO = 96,
/* Elecrow CrowPanel Advance models, ESP32-S3 and TFT with SX1262 radio plugin */
meshtastic_HardwareModel_CROWPANEL = 97,
/* *
Lilygo LINK32 board with sensors */
/* Lilygo LINK32 board with sensors */
meshtastic_HardwareModel_LINK_32 = 98,
/* *
Seeed Tracker L1 */
/* Seeed Tracker L1 */
meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 = 99,
/* *
Seeed Tracker L1 EINK driver */
/* Seeed Tracker L1 EINK driver */
meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK = 100,
/* Reserved ID for future and past use */
meshtastic_HardwareModel_QWANTZ_TINY_ARMS = 101,
/* *
Lilygo T-Deck Pro */
/* Lilygo T-Deck Pro */
meshtastic_HardwareModel_T_DECK_PRO = 102,
/* *
Lilygo TLora Pager */
/* Lilygo TLora Pager */
meshtastic_HardwareModel_T_LORA_PAGER = 103,
/* *
GAT562 Mesh Trial Tracker */
/* GAT562 Mesh Trial Tracker */
meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
/* *
RAKwireless WisMesh Tag */
/* RAKwireless WisMesh Tag */
meshtastic_HardwareModel_WISMESH_TAG = 105,
/* *
RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
/* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
meshtastic_HardwareModel_RAK3312 = 106,
/* Elecrow ThinkNode M5 https://www.elecrow.com/wiki/ThinkNode_M5_Meshtastic_LoRa_Signal_Transceiver_ESP32-S3.html */
meshtastic_HardwareModel_THINKNODE_M5 = 107,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@ -432,7 +426,10 @@ typedef enum _meshtastic_Routing_Error {
/* Admin packet otherwise checks out, but uses a bogus or expired session key */
meshtastic_Routing_Error_ADMIN_BAD_SESSION_KEY = 36,
/* Admin packet sent using PKC, but not from a public key on the admin key list */
meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED = 37
meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED = 37,
/* Airtime fairness rate limit exceeded for a packet
This typically enforced per portnum and is used to prevent a single node from monopolizing airtime */
meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED = 38
} meshtastic_Routing_Error;
/* The priority of this message for sending.
@ -1228,8 +1225,8 @@ extern "C" {
#define _meshtastic_Position_AltSource_ARRAYSIZE ((meshtastic_Position_AltSource)(meshtastic_Position_AltSource_ALT_BAROMETRIC+1))
#define _meshtastic_Routing_Error_MIN meshtastic_Routing_Error_NONE
#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED
#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED+1))
#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED
#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED+1))
#define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET
#define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX

View File

@ -11,7 +11,7 @@
/* Enum definitions */
/* Any significant power changing event in meshtastic should be tagged with a powermon state transition.
If you are making new meshtastic features feel free to add new entries at the end of this definition. */
If you are making new meshtastic features feel free to add new entries at the end of this definition. */
typedef enum _meshtastic_PowerMon_State {
meshtastic_PowerMon_State_None = 0,
meshtastic_PowerMon_State_CPU_DeepSleep = 1,
@ -34,13 +34,13 @@ something like "S:PM:C,0x00001234,REASON" where the hex number is the bitmask of
meshtastic_PowerMon_State_Screen_Drawing = 512,
meshtastic_PowerMon_State_Wifi_On = 1024,
/* GPS is actively trying to find our location
See GPSPowerState for more details */
See GPSPowerState for more details */
meshtastic_PowerMon_State_GPS_Active = 2048
} meshtastic_PowerMon_State;
/* What operation would we like the UUT to perform.
note: senders should probably set want_response in their request packets, so that they can know when the state
machine has started processing their request */
note: senders should probably set want_response in their request packets, so that they can know when the state
machine has started processing their request */
typedef enum _meshtastic_PowerStressMessage_Opcode {
/* Unset/unused */
meshtastic_PowerStressMessage_Opcode_UNSET = 0,
@ -67,7 +67,7 @@ typedef enum _meshtastic_PowerStressMessage_Opcode {
/* Struct definitions */
/* Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs).
But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) */
But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) */
typedef struct _meshtastic_PowerMon {
char dummy_field;
} meshtastic_PowerMon;

View File

@ -93,7 +93,13 @@ typedef enum _meshtastic_TelemetrySensorType {
/* PCT2075 Temperature Sensor */
meshtastic_TelemetrySensorType_PCT2075 = 39,
/* ADS1X15 ADC */
meshtastic_TelemetrySensorType_ADS1X15 = 40
meshtastic_TelemetrySensorType_ADS1X15 = 40,
/* ADS1X15 ADC_ALT */
meshtastic_TelemetrySensorType_ADS1X15_ALT = 41,
/* Sensirion SFA30 Formaldehyde sensor */
meshtastic_TelemetrySensorType_SFA30 = 42,
/* SEN5X PM SENSORS */
meshtastic_TelemetrySensorType_SEN5X = 43
} meshtastic_TelemetrySensorType;
/* Struct definitions */
@ -242,40 +248,40 @@ typedef struct _meshtastic_PowerMetrics {
/* Air quality metrics */
typedef struct _meshtastic_AirQualityMetrics {
/* Concentration Units Standard PM1.0 */
/* Concentration Units Standard PM1.0 in ug/m3 */
bool has_pm10_standard;
uint32_t pm10_standard;
/* Concentration Units Standard PM2.5 */
/* Concentration Units Standard PM2.5 in ug/m3 */
bool has_pm25_standard;
uint32_t pm25_standard;
/* Concentration Units Standard PM10.0 */
/* Concentration Units Standard PM10.0 in ug/m3 */
bool has_pm100_standard;
uint32_t pm100_standard;
/* Concentration Units Environmental PM1.0 */
/* Concentration Units Environmental PM1.0 in ug/m3 */
bool has_pm10_environmental;
uint32_t pm10_environmental;
/* Concentration Units Environmental PM2.5 */
/* Concentration Units Environmental PM2.5 in ug/m3 */
bool has_pm25_environmental;
uint32_t pm25_environmental;
/* Concentration Units Environmental PM10.0 */
/* Concentration Units Environmental PM10.0 in ug/m3 */
bool has_pm100_environmental;
uint32_t pm100_environmental;
/* 0.3um Particle Count */
/* 0.3um Particle Count in #/0.1l */
bool has_particles_03um;
uint32_t particles_03um;
/* 0.5um Particle Count */
/* 0.5um Particle Count in #/0.1l */
bool has_particles_05um;
uint32_t particles_05um;
/* 1.0um Particle Count */
/* 1.0um Particle Count in #/0.1l */
bool has_particles_10um;
uint32_t particles_10um;
/* 2.5um Particle Count */
/* 2.5um Particle Count in #/0.1l */
bool has_particles_25um;
uint32_t particles_25um;
/* 5.0um Particle Count */
/* 5.0um Particle Count in #/0.1l */
bool has_particles_50um;
uint32_t particles_50um;
/* 10.0um Particle Count */
/* 10.0um Particle Count in #/0.1l */
bool has_particles_100um;
uint32_t particles_100um;
/* CO2 concentration in ppm */
@ -287,6 +293,36 @@ typedef struct _meshtastic_AirQualityMetrics {
/* CO2 sensor relative humidity in % */
bool has_co2_humidity;
float co2_humidity;
/* Formaldehyde sensor formaldehyde concentration in ppb */
bool has_form_formaldehyde;
float form_formaldehyde;
/* Formaldehyde sensor relative humidity in %RH */
bool has_form_humidity;
float form_humidity;
/* Formaldehyde sensor temperature in degrees Celsius */
bool has_form_temperature;
float form_temperature;
/* Concentration Units Standard PM4.0 in ug/m3 */
bool has_pm40_standard;
uint32_t pm40_standard;
/* 4.0um Particle Count in #/0.1l */
bool has_particles_40um;
uint32_t particles_40um;
/* PM Sensor Temperature */
bool has_pm_temperature;
float pm_temperature;
/* PM Sensor humidity */
bool has_pm_humidity;
float pm_humidity;
/* PM Sensor VOC Index */
bool has_pm_voc_idx;
float pm_voc_idx;
/* PM Sensor NOx Index */
bool has_pm_nox_idx;
float pm_nox_idx;
/* Typical Particle Size in um */
bool has_particles_tps;
float particles_tps;
} meshtastic_AirQualityMetrics;
/* Local device mesh statistics */
@ -398,8 +434,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_ADS1X15
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_ADS1X15+1))
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SEN5X
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SEN5X+1))
@ -415,7 +451,7 @@ extern "C" {
#define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
@ -424,7 +460,7 @@ extern "C" {
#define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
@ -490,6 +526,16 @@ extern "C" {
#define meshtastic_AirQualityMetrics_co2_tag 13
#define meshtastic_AirQualityMetrics_co2_temperature_tag 14
#define meshtastic_AirQualityMetrics_co2_humidity_tag 15
#define meshtastic_AirQualityMetrics_form_formaldehyde_tag 16
#define meshtastic_AirQualityMetrics_form_humidity_tag 17
#define meshtastic_AirQualityMetrics_form_temperature_tag 18
#define meshtastic_AirQualityMetrics_pm40_standard_tag 19
#define meshtastic_AirQualityMetrics_particles_40um_tag 20
#define meshtastic_AirQualityMetrics_pm_temperature_tag 21
#define meshtastic_AirQualityMetrics_pm_humidity_tag 22
#define meshtastic_AirQualityMetrics_pm_voc_idx_tag 23
#define meshtastic_AirQualityMetrics_pm_nox_idx_tag 24
#define meshtastic_AirQualityMetrics_particles_tps_tag 25
#define meshtastic_LocalStats_uptime_seconds_tag 1
#define meshtastic_LocalStats_channel_utilization_tag 2
#define meshtastic_LocalStats_air_util_tx_tag 3
@ -597,7 +643,17 @@ X(a, STATIC, OPTIONAL, UINT32, particles_50um, 11) \
X(a, STATIC, OPTIONAL, UINT32, particles_100um, 12) \
X(a, STATIC, OPTIONAL, UINT32, co2, 13) \
X(a, STATIC, OPTIONAL, FLOAT, co2_temperature, 14) \
X(a, STATIC, OPTIONAL, FLOAT, co2_humidity, 15)
X(a, STATIC, OPTIONAL, FLOAT, co2_humidity, 15) \
X(a, STATIC, OPTIONAL, FLOAT, form_formaldehyde, 16) \
X(a, STATIC, OPTIONAL, FLOAT, form_humidity, 17) \
X(a, STATIC, OPTIONAL, FLOAT, form_temperature, 18) \
X(a, STATIC, OPTIONAL, UINT32, pm40_standard, 19) \
X(a, STATIC, OPTIONAL, UINT32, particles_40um, 20) \
X(a, STATIC, OPTIONAL, FLOAT, pm_temperature, 21) \
X(a, STATIC, OPTIONAL, FLOAT, pm_humidity, 22) \
X(a, STATIC, OPTIONAL, FLOAT, pm_voc_idx, 23) \
X(a, STATIC, OPTIONAL, FLOAT, pm_nox_idx, 24) \
X(a, STATIC, OPTIONAL, FLOAT, particles_tps, 25)
#define meshtastic_AirQualityMetrics_CALLBACK NULL
#define meshtastic_AirQualityMetrics_DEFAULT NULL
@ -686,7 +742,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size
#define meshtastic_AirQualityMetrics_size 88
#define meshtastic_AirQualityMetrics_size 150
#define meshtastic_DeviceMetrics_size 27
#define meshtastic_EnvironmentMetrics_size 113
#define meshtastic_HealthMetrics_size 11

View File

@ -4,8 +4,13 @@
#include "main.h"
#include "mesh/Router.h"
#include <AsyncUDP.h>
#if HAS_ETHERNET && defined(ARCH_NRF52)
#include "mesh/eth/ethClient.h"
#else
#include <WiFi.h>
#endif
#include <AsyncUDP.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
@ -22,11 +27,11 @@ class UdpMulticastHandler final
void start()
{
if (udp.listenMulticast(udpIpAddress, UDP_MULTICAST_DEFAUL_PORT, 64)) {
#ifndef ARCH_PORTDUINO
// FIXME(PORTDUINO): arduino lacks IPAddress::toString()
LOG_DEBUG("UDP Listening on IP: %s", WiFi.localIP().toString().c_str());
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO)
LOG_DEBUG("UDP Listening on IP: %u.%u.%u.%u:%u", udpIpAddress[0], udpIpAddress[1], udpIpAddress[2], udpIpAddress[3],
UDP_MULTICAST_DEFAUL_PORT);
#else
LOG_DEBUG("UDP Listening");
LOG_DEBUG("UDP Listening on IP: %s", WiFi.localIP().toString().c_str());
#endif
udp.onPacket([this](AsyncUDPPacket packet) { onReceive(packet); });
} else {
@ -37,7 +42,10 @@ class UdpMulticastHandler final
void onReceive(AsyncUDPPacket packet)
{
size_t packetLength = packet.length();
#ifndef ARCH_PORTDUINO
#if defined(ARCH_NRF52)
IPAddress ip = packet.remoteIP();
LOG_DEBUG("UDP broadcast from: %u.%u.%u.%u, len=%u", ip[0], ip[1], ip[2], ip[3], packetLength);
#elif !defined(ARCH_PORTDUINO)
// FIXME(PORTDUINO): arduino lacks IPAddress::toString()
LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength);
#endif
@ -61,7 +69,11 @@ class UdpMulticastHandler final
if (!mp || !udp) {
return false;
}
#ifndef ARCH_PORTDUINO
#if defined(ARCH_NRF52)
if (!isEthernetAvailable()) {
return false;
}
#elif !defined(ARCH_PORTDUINO)
if (WiFi.status() != WL_CONNECTED) {
return false;
}

View File

@ -850,7 +850,13 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
this->waitingForAck = true;
// Log outgoing message
LOG_INFO("Send message id=%d, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes);
LOG_INFO("Send message id=%u, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes);
if (p->to != 0xffffffff) {
LOG_INFO("Proactively adding %x as favorite node", p->to);
nodeDB->set_favorite(true, p->to);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
// Send to mesh and phone (even if no phone connected, to track ACKs)
service->sendToMesh(p, RX_SRC_LOCAL, true);
@ -1441,7 +1447,7 @@ void CannedMessageModule::drawEmotePickerScreen(OLEDDisplay *display, OLEDDispla
int headerY = y;
int listTop = headerY + headerFontHeight + headerMargin;
int visibleRows = (display->getHeight() - listTop - 2) / rowHeight;
int _visibleRows = (display->getHeight() - listTop - 2) / rowHeight;
int numEmotes = graphics::numEmotes;
// Clamp highlight index
@ -1451,11 +1457,11 @@ void CannedMessageModule::drawEmotePickerScreen(OLEDDisplay *display, OLEDDispla
emotePickerIndex = numEmotes - 1;
// Determine which emote is at the top
int topIndex = emotePickerIndex - visibleRows / 2;
int topIndex = emotePickerIndex - _visibleRows / 2;
if (topIndex < 0)
topIndex = 0;
if (topIndex > numEmotes - visibleRows)
topIndex = std::max(0, numEmotes - visibleRows);
if (topIndex > numEmotes - _visibleRows)
topIndex = std::max(0, numEmotes - _visibleRows);
// Draw header/title
display->setFont(FONT_SMALL);
@ -1703,7 +1709,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
} else {
// Text: split by words and wrap inside word if needed
String text = token.second;
uint16_t pos = 0;
pos = 0;
while (pos < text.length()) {
// Find next space (or end)
int spacePos = text.indexOf(' ', pos);
@ -1747,7 +1753,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
int yLine = inputY;
for (auto &line : lines) {
int nextX = x;
for (auto &token : line) {
for (const auto &token : line) {
if (token.first) {
const graphics::Emote *emote = nullptr;
for (int j = 0; j < graphics::numEmotes; j++) {
@ -1783,19 +1789,20 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
int topMsg;
std::vector<int> rowHeights;
int visibleRows;
int _visibleRows;
// Draw header (To: ...)
drawHeader(display, x, y, buffer);
// Shift message list upward by 3 pixels to reduce spacing between header and first message
const int listYOffset = y + FONT_HEIGHT_SMALL - 3;
visibleRows = (display->getHeight() - listYOffset) / baseRowSpacing;
_visibleRows = (display->getHeight() - listYOffset) / baseRowSpacing;
// Figure out which messages are visible and their needed heights
topMsg =
(messagesCount > visibleRows && currentMessageIndex >= visibleRows - 1) ? currentMessageIndex - visibleRows + 2 : 0;
int countRows = std::min(messagesCount, visibleRows);
topMsg = (messagesCount > _visibleRows && currentMessageIndex >= _visibleRows - 1)
? currentMessageIndex - _visibleRows + 2
: 0;
int countRows = std::min(messagesCount, _visibleRows);
// --- Build per-row max height based on all emotes in line ---
for (int i = 0; i < countRows; i++) {
@ -1822,7 +1829,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
int lineY = yCursor;
const char *msg = getMessageByIndex(msgIdx);
int rowHeight = rowHeights[vis];
bool highlight = (msgIdx == currentMessageIndex);
bool _highlight = (msgIdx == currentMessageIndex);
// --- Multi-emote tokenization ---
std::vector<std::pair<bool, String>> tokens; // (isEmote, token)
@ -1875,20 +1882,20 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
int textYOffset = (rowHeight - FONT_HEIGHT_SMALL) / 2;
#ifdef USE_EINK
int nextX = x + (highlight ? 12 : 0);
if (highlight)
int nextX = x + (_highlight ? 12 : 0);
if (_highlight)
display->drawString(x + 0, lineY + textYOffset, ">");
#else
int scrollPadding = 8;
if (highlight) {
if (_highlight) {
display->fillRect(x + 0, lineY, display->getWidth() - scrollPadding, rowHeight);
display->setColor(BLACK);
}
int nextX = x + (highlight ? 2 : 0);
int nextX = x + (_highlight ? 2 : 0);
#endif
// Draw all tokens left to right
for (auto &token : tokens) {
for (const auto &token : tokens) {
if (token.first) {
// Emote
const graphics::Emote *emote = nullptr;
@ -1910,7 +1917,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
}
}
#ifndef USE_EINK
if (highlight)
if (_highlight)
display->setColor(WHITE);
#endif
@ -1918,11 +1925,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
}
// Scrollbar
if (messagesCount > visibleRows) {
if (messagesCount > _visibleRows) {
int scrollHeight = display->getHeight() - listYOffset;
int scrollTrackX = display->getWidth() - 6;
display->drawRect(scrollTrackX, listYOffset, 4, scrollHeight);
int barHeight = (scrollHeight * visibleRows) / messagesCount;
int barHeight = (scrollHeight * _visibleRows) / messagesCount;
int scrollPos = listYOffset + (scrollHeight * topMsg) / messagesCount;
display->fillRect(scrollTrackX, scrollPos, 4, barHeight);
}

View File

@ -126,9 +126,11 @@ int32_t ExternalNotificationModule::runOnce()
millis()) {
setExternalState(1, !getExternal(1));
}
if (externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
// Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL)
if (!moduleConfig.external_notification.use_pwm &&
externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
millis()) {
LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms,
millis());
setExternalState(2, !getExternal(2));
@ -247,7 +249,8 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
digitalWrite(moduleConfig.external_notification.output_vibra, on);
break;
case 2:
if (moduleConfig.external_notification.output_buzzer)
// Only control buzzer pin digitally if not using PWM mode
if (moduleConfig.external_notification.output_buzzer && !moduleConfig.external_notification.use_pwm)
digitalWrite(moduleConfig.external_notification.output_buzzer, on);
break;
default:
@ -320,6 +323,11 @@ void ExternalNotificationModule::stopNow()
#endif
nagCycleCutoff = 1; // small value
isNagging = false;
// Turn off all outputs
for (int i = 0; i < 3; i++) {
setExternalState(i, false);
externalTurnedOn[i] = 0;
}
setIntervalFromNow(0);
#ifdef T_WATCH_S3
drv.stop();
@ -478,14 +486,17 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
if (containsBell) {
LOG_INFO("externalNotificationModule - Notification Bell (Buzzer)");
isNagging = true;
if (!moduleConfig.external_notification.use_pwm) {
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
setExternalState(2, true);
} else {
#ifdef HAS_I2S
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
#else
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
} else
#endif
if (moduleConfig.external_notification.use_pwm) {
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
}
}
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
@ -526,10 +537,11 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
#ifdef HAS_I2S
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
}
#else
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
} else
#endif
if (moduleConfig.external_notification.use_pwm) {
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
}
}
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;

View File

@ -14,6 +14,11 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
{
auto p = *pptr;
if (p.is_licensed != owner.is_licensed) {
LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
return true;
}
// Coerce user.id to be derived from the node number
snprintf(p.id, sizeof(p.id), "!%08x", getFrom(&mp));

View File

@ -266,9 +266,11 @@ meshtastic_MeshPacket *PositionModule::allocPositionPacket()
LOG_INFO("Position packet: time=%i lat=%i lon=%i", p.time, p.latitude_i, p.longitude_i);
#ifndef MESHTASTIC_EXCLUDE_ATAK
// TAK Tracker devices should send their position in a TAK packet over the ATAK port
if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)
return allocAtakPli();
#endif
return allocDataProtobuf(p);
}

View File

@ -107,11 +107,7 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event)
return true;
// Power control
case INPUT_BROKER_SHUTDOWN:
LOG_ERROR("Shutting Down");
IF_SCREEN(screen->showSimpleBanner("Shutting Down..."));
nodeDB->saveToDisk();
shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000;
// runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
shutdownAtMsec = millis();
return true;
default:

View File

@ -559,10 +559,8 @@ void MQTT::sendSubscriptions()
int32_t MQTT::runOnce()
{
#if HAS_NETWORKING
if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()))
return disable();
bool wantConnection = wantsLink();
perhapsReportToMap();
@ -572,7 +570,7 @@ int32_t MQTT::runOnce()
publishQueuedMessages();
return 200;
}
#if HAS_NETWORKING
else if (!pubSub.loop()) {
if (!wantConnection)
return 5000; // If we don't want connection now, check again in 5 secs
@ -596,8 +594,10 @@ int32_t MQTT::runOnce()
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth)
return 20;
}
#endif
#else
// No networking available, return default interval
return 30000;
#endif
}
bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTClient *client)

View File

@ -188,6 +188,8 @@
#define HW_VENDOR meshtastic_HardwareModel_RAK3312
#elif defined(LINK_32)
#define HW_VENDOR meshtastic_HardwareModel_LINK_32
#elif defined(T_DECK_PRO)
#define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO
#endif
// -----------------------------------------------------------------------------

View File

@ -0,0 +1,28 @@
#include "configuration.h"
#ifdef T_DECK_PRO
#include "input/TouchScreenImpl1.h"
#include <CSE_CST328.h>
#include <Wire.h>
CSE_CST328 tsPanel = CSE_CST328(EINK_WIDTH, EINK_HEIGHT, &Wire, CST328_PIN_RST, CST328_PIN_INT);
bool readTouch(int16_t *x, int16_t *y)
{
if (tsPanel.getTouches()) {
*x = tsPanel.getPoint(0).x;
*y = tsPanel.getPoint(0).y;
return true;
}
return false;
}
// T-Deck Pro specific init
void lateInitVariant()
{
tsPanel.begin();
touchScreenImpl1 = new TouchScreenImpl1(EINK_WIDTH, EINK_HEIGHT, readTouch);
touchScreenImpl1->init();
}
#endif

View File

@ -0,0 +1,73 @@
#include "AsyncUDP.h"
#if HAS_ETHERNET
AsyncUDP::AsyncUDP() : OSThread("AsyncUDP"), localPort(0) {}
bool AsyncUDP::listenMulticast(IPAddress multicastIP, uint16_t port, uint8_t ttl)
{
if (!isMulticast(multicastIP))
return false;
localPort = port;
udp.beginMulticast(multicastIP, port);
return true;
}
size_t AsyncUDP::write(uint8_t b)
{
return udp.write(&b, 1);
}
size_t AsyncUDP::write(const uint8_t *data, size_t len)
{
return udp.write(data, len);
}
void AsyncUDP::onPacket(const std::function<void(AsyncUDPPacket)> &callback)
{
_onPacket = callback;
}
bool AsyncUDP::writeTo(const uint8_t *data, size_t len, IPAddress ip, uint16_t port)
{
if (!udp.beginPacket(ip, port))
return false;
udp.write(data, len);
return udp.endPacket();
}
// AsyncUDPPacket
AsyncUDPPacket::AsyncUDPPacket(EthernetUDP &source) : _udp(source), _remoteIP(source.remoteIP()), _remotePort(source.remotePort())
{
if (_udp.available() > 0) {
_readLength = _udp.read(_buffer, sizeof(_buffer));
} else {
_readLength = 0;
}
}
IPAddress AsyncUDPPacket::remoteIP()
{
return _remoteIP;
}
uint16_t AsyncUDPPacket::length()
{
return _readLength;
}
const uint8_t *AsyncUDPPacket::data()
{
return _buffer;
}
int32_t AsyncUDP::runOnce()
{
if (_onPacket && udp.parsePacket() > 0) {
AsyncUDPPacket packet(udp);
_onPacket(packet);
}
return 5; // check every 5ms
}
#endif // HAS_ETHERNET

View File

@ -0,0 +1,63 @@
#ifndef ASYNC_UDP_H
#define ASYNC_UDP_H
#include "configuration.h"
#if HAS_ETHERNET
#include "concurrency/OSThread.h"
#include <IPAddress.h>
#include <Print.h>
#include <RAK13800_W5100S.h>
#include <cstdint>
#include <functional>
class AsyncUDPPacket;
class AsyncUDP : public Print, private concurrency::OSThread
{
public:
AsyncUDP();
explicit operator bool() const { return localPort != 0; }
bool listenMulticast(IPAddress multicastIP, uint16_t port, uint8_t ttl = 64);
bool writeTo(const uint8_t *data, size_t len, IPAddress ip, uint16_t port);
size_t write(uint8_t b) override;
size_t write(const uint8_t *data, size_t len) override;
void onPacket(const std::function<void(AsyncUDPPacket)> &callback);
private:
EthernetUDP udp;
uint16_t localPort;
std::function<void(AsyncUDPPacket)> _onPacket;
virtual int32_t runOnce() override;
};
class AsyncUDPPacket
{
public:
AsyncUDPPacket(EthernetUDP &source);
IPAddress remoteIP();
uint16_t length();
const uint8_t *data();
private:
EthernetUDP &_udp;
IPAddress _remoteIP;
uint16_t _remotePort;
size_t _readLength = 0;
static constexpr size_t BUF_SIZE = 512;
uint8_t _buffer[BUF_SIZE];
};
inline bool isMulticast(const IPAddress &ip)
{
return (ip[0] & 0xF0) == 0xE0;
}
#endif // HAS_ETHERNET
#endif // ASYNC_UDP_H

View File

@ -49,6 +49,8 @@
#define HW_VENDOR meshtastic_HardwareModel_RAK2560
#elif defined(WISMESH_TAP)
#define HW_VENDOR meshtastic_HardwareModel_WISMESH_TAP
#elif defined(WISMESH_TAG)
#define HW_VENDOR meshtastic_HardwareModel_WISMESH_TAG
#elif defined(GAT562_MESH_TRIAL_TRACKER)
#define HW_VENDOR meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER
#elif defined(RAK4630)
@ -89,6 +91,8 @@
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_POCKET
#elif defined(NOMADSTAR_METEOR_PRO)
#define HW_VENDOR meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO
#elif defined(SEEED_WIO_TRACKER_L1_EINK)
#define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK
#elif defined(SEEED_WIO_TRACKER_L1)
#define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1
#else

View File

@ -126,6 +126,8 @@ class Power : private concurrency::OSThread
bool analogInit();
/// Setup a Lipo battery level sensor
bool lipoInit();
/// Setup a Lipo charger
bool lipoChargerInit();
private:
// open circuit voltage lookup table

View File

@ -40,21 +40,8 @@ void powerCommandsCheck()
#endif
}
#if defined(ARCH_ESP32) || defined(ARCH_NRF52)
if (shutdownAtMsec && screen) {
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
}
#endif
if (shutdownAtMsec && millis() > shutdownAtMsec) {
LOG_INFO("Shut down from admin command");
#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
playShutdownMelody();
shutdownAtMsec = 0;
power->shutdown();
#elif defined(ARCH_PORTDUINO)
exit(EXIT_SUCCESS);
#else
LOG_WARN("FIXME implement shutdown for this platform");
#endif
}
}

View File

@ -1,156 +0,0 @@
; Meshtastic DIY v1 by Nano VHF Schematic based on ESP32-WROOM-32 (38 pins) devkit & EBYTE E22 SX1262/SX1268 module
[env:meshtastic-diy-v1]
extends = esp32_base
board = esp32doit-devkit-v1
board_check = true
build_flags =
${esp32_base.build_flags}
-D DIY_V1
-D EBYTE_E22
-I variants/diy/v1
; Meshtastic DIY v1.1 new schematic based on ESP32-WROOM-32 & SX1262/SX1268 modules
[env:meshtastic-diy-v1_1]
extends = esp32_base
board = esp32doit-devkit-v1
board_level = extra
build_flags =
${esp32_base.build_flags}
-D DIY_V1
-D EBYTE_E22
-I variants/diy/v1_1
; Port to Disaster Radio's ESP32-v3 Dev Board
[env:meshtastic-dr-dev]
extends = esp32_base
board = esp32doit-devkit-v1
board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 532480
build_flags =
${esp32_base.build_flags}
-D DR_DEV
-D EBYTE_E22
-I variants/diy/dr-dev
; Hydra - Meshtastic DIY v1 hardware with some specific changes
[env:hydra]
extends = esp32_base
board = esp32doit-devkit-v1
build_flags =
${esp32_base.build_flags}
-D DIY_V1
-I variants/diy/hydra
; Promicro + E22(0)-xxxMM / RA-01SH modules board variant - DIY - without TCXO
[env:nrf52_promicro_diy_xtal]
extends = nrf52840_base
board = promicro-nrf52840
board_level = extra
build_flags = ${nrf52840_base.build_flags}
-I variants/diy/nrf52_promicro_diy_xtal
-D NRF52_PROMICRO_DIY
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_xtal>
lib_deps =
${nrf52840_base.lib_deps}
debug_tool = jlink
; Promicro + E22(0)-xxxM / HT-RA62 modules board variant - DIY - with TCXO
[env:nrf52_promicro_diy_tcxo]
extends = nrf52840_base
board = promicro-nrf52840
build_flags = ${nrf52840_base.build_flags}
-I variants/diy/nrf52_promicro_diy_tcxo
-D NRF52_PROMICRO_DIY
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/nrf52_promicro_diy_tcxo>
lib_deps =
${nrf52840_base.lib_deps}
debug_tool = jlink
; NRF52 ProMicro w/ E-Ink display
[env:nrf52_promicro_diy-inkhud]
board_level = extra
extends = nrf52840_base, inkhud
board = promicro-nrf52840
build_flags =
${nrf52840_base.build_flags}
${inkhud.build_flags}
-I variants/diy/nrf52_promicro_diy_tcxo
-D NRF52_PROMICRO_DIY
build_src_filter =
${nrf52_base.build_src_filter}
${inkhud.build_src_filter}
+<../variants/diy/nrf52_promicro_diy_tcxo>
lib_deps =
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
${nrf52840_base.lib_deps}
extra_scripts =
${env.extra_scripts}
variants/diy/nrf52_promicro_diy_tcxo/custom_build_tasks.py ; Add to PIO's Project Tasks pane: preset builds for common displays
; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893
[env:xiao_ble]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DXIAO_BLE_LEGACY_PINOUT -DEBYTE_E22 -DEBYTE_E22_900M30S
build_unflags = -DGPS_L76K
; Seeed XIAO nRF52840 + EBYTE E22-900M30S - Pinout matching Wio-SX1262 (SKU 113010003)
[env:seeed_xiao_nrf52840_e22_900m30s]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DEBYTE_E22 -DEBYTE_E22_900M30S
build_unflags = -DGPS_L76K
; Seeed XIAO nRF52840 + EBYTE E22-900M33S - Pinout matching Wio-SX1262 (SKU 113010003)
[env:seeed_xiao_nrf52840_e22_900m33s]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DEBYTE_E22 -DEBYTE_E22_900M33S
build_unflags = -DGPS_L76K
; Seeed XIAO nRF52840 + XIAO Wio SX1262 DIY
[env:seeed-xiao-nrf52840-wio-sx1262]
board = xiao_ble_sense
extends = nrf52840_base
board_level = extra
build_flags = ${nrf52840_base.build_flags} -Ivariants/diy/seeed-xiao-nrf52840-wio-sx1262 -D PRIVATE_HW
-Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/seeed-xiao-nrf52840-wio-sx1262>
lib_deps =
${nrf52840_base.lib_deps}
debug_tool = jlink
; NanoVHF T-Energy-S3 + E22(0)-xxxM - DIY
[env:t-energy-s3_e22]
extends = esp32s3_base
board = esp32-s3-devkitc-1
board_build.partitions = default_16MB.csv
board_level = extra
board_upload.flash_size = 16MB ;Specify the FLASH capacity as 16MB
board_build.arduino.memory_type = qio_opi ;Enable internal PSRAM
build_unflags =
${esp32s3_base.build_unflags}
-D ARDUINO_USB_MODE=1
build_flags =
${esp32s3_base.build_flags}
-D EBYTE_ESP32_S3
-D BOARD_HAS_PSRAM
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-I variants/diy/t-energy-s3_e22
; ESP32 C3 Super Mini Development Board
; https://www.espboards.dev/esp32/esp32-c3-super-mini/
[env:esp32c3_super_mini]
extends = esp32c3_base
board = esp32-c3-devkitm-1
build_flags =
${esp32_base.build_flags}
-D PRIVATE_HW
-I variants/diy/esp32c3_super_mini
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1
board_level = extra

View File

@ -8,11 +8,11 @@ build_flags =
-D VTABLES_IN_FLASH=1
-D CONFIG_DISABLE_HAL_LOCKS=1
-O2
-I variants/betafpv_2400_tx_micro
-I variants/esp32/betafpv_2400_tx_micro
board_build.f_cpu = 240000000L
upload_protocol = esptool
;upload_port = /dev/ttyUSB0
upload_speed = 460800
lib_deps =
${esp32_base.lib_deps}
adafruit/Adafruit NeoPixel @ ^1.12.0
adafruit/Adafruit NeoPixel @ ^1.12.0

View File

@ -8,10 +8,10 @@ build_flags =
-D VTABLES_IN_FLASH=1
-D CONFIG_DISABLE_HAL_LOCKS=1
-O2
-I variants/betafpv_900_tx_nano
-I variants/esp32/betafpv_900_tx_nano
board_build.f_cpu = 240000000L
upload_protocol = esptool
;upload_port = /dev/ttyUSB0
upload_speed = 460800
lib_deps =
${esp32_base.lib_deps}
${esp32_base.lib_deps}

View File

@ -5,7 +5,7 @@ board = esp32doit-devkit-v1
build_flags =
${esp32_base.build_flags}
-D CHATTER_2
-I variants/chatter2
-I variants/esp32/chatter2
lib_deps =
${esp32_base.lib_deps}

View File

@ -0,0 +1,11 @@
; Port to Disaster Radio's ESP32-v3 Dev Board
[env:meshtastic-dr-dev]
extends = esp32_base
board = esp32doit-devkit-v1
board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 532480
build_flags =
${esp32_base.build_flags}
-D DR_DEV
-D EBYTE_E22
-I variants/esp32/diy/dr-dev

View File

@ -0,0 +1,8 @@
; Hydra - Meshtastic DIY v1 hardware with some specific changes
[env:hydra]
extends = esp32_base
board = esp32doit-devkit-v1
build_flags =
${esp32_base.build_flags}
-D DIY_V1
-I variants/esp32/diy/hydra

View File

@ -0,0 +1,10 @@
; Meshtastic DIY v1 by Nano VHF Schematic based on ESP32-WROOM-32 (38 pins) devkit & EBYTE E22 SX1262/SX1268 module
[env:meshtastic-diy-v1]
extends = esp32_base
board = esp32doit-devkit-v1
board_check = true
build_flags =
${esp32_base.build_flags}
-D DIY_V1
-D EBYTE_E22
-I variants/esp32/diy/v1

View File

@ -0,0 +1,10 @@
; Meshtastic DIY v1.1 new schematic based on ESP32-WROOM-32 & SX1262/SX1268 modules
[env:meshtastic-diy-v1_1]
extends = esp32_base
board = esp32doit-devkit-v1
board_level = extra
build_flags =
${esp32_base.build_flags}
-D DIY_V1
-D EBYTE_E22
-I variants/esp32/diy/v1_1

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