Compare commits

..

6 Commits

129 changed files with 1160 additions and 3921 deletions

View File

@@ -11,6 +11,11 @@ runs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Uncomment build epoch
shell: bash
run: |
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
- name: Install dependencies
shell: bash
run: |

View File

@@ -18,7 +18,7 @@ permissions: read-all
jobs:
pio-build:
name: build-${{ inputs.platform }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
with:
@@ -45,7 +45,7 @@ jobs:
- name: Build ${{ inputs.platform }}
id: build
uses: fifieldt/gh-action-firmware@fifield
uses: meshtastic/gh-action-firmware@main
with:
pio_platform: ${{ inputs.platform }}
pio_env: ${{ inputs.pio_env }}

View File

@@ -1,500 +0,0 @@
name: Build One Arch
on:
workflow_dispatch:
inputs:
arch:
type: choice
options:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- native
jobs:
setup:
strategy:
fail-fast: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
run: |
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra)
else
TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr)
fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
outputs:
esp32: ${{ steps.jsonStep.outputs.esp32 }}
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
stm32: ${{ steps.jsonStep.outputs.stm32 }}
check: ${{ steps.jsonStep.outputs.check }}
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- 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 }}
build-esp32:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32
build-esp32s3:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32s3
build-esp32c3:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c3
build-esp32c6:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c6
build-nrf52840:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: nrf52840
build-rp2040:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2040
build-rp2350:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2350
build-stm32:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: stm32
build-debian-src:
if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
build_location: local
secrets: inherit
package-pio-deps-native-tft:
if: ${{ inputs.arch == 'native' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
secrets: inherit
test-native:
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' }}
uses: ./.github/workflows/test_native.yml
docker-deb-amd64:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-deb-amd64-tft:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
pio_env: native-tft
docker-alp-amd64:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-alp-amd64-tft:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
pio_env: native-tft
docker-deb-arm64:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm64
runs-on: ubuntu-24.04-arm
push: false
docker-deb-armv7:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm/v7
runs-on: ubuntu-24.04-arm
push: false
gather-artifacts:
permissions:
contents: write
pull-requests: write
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest
needs:
[
version,
build-esp32,
build-esp32s3,
build-esp32c3,
build-esp32c6,
build-nrf52840,
build-rp2040,
build-rp2350,
build-stm32,
]
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v5
with:
path: ./
pattern: firmware-${{inputs.arch}}-*
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- 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-${{inputs.arch}}-${{ needs.version.outputs.long }}
overwrite: true
path: |
./firmware-*.bin
./firmware-*.uf2
./firmware-*.hex
./firmware-*-ota.zip
./device-*.sh
./device-*.bat
./littlefs-*.bin
./bleota*bin
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v5
with:
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
# For diagnostics
- name: Show artifacts
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v4
with:
name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
overwrite: true
path: ./*.elf
retention-days: 30
- uses: scruplelesswizard/comment-artifact@main
if: ${{ github.event_name == 'pull_request' }}
with:
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}
release-artifacts:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
- version
- gather-artifacts
- build-debian-src
- package-pio-deps-native-tft
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Create release
uses: softprops/action-gh-release@v2
id: create_release
with:
draft: true
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
uses: actions/download-artifact@v5
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v5
with:
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-${{ 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
run: ls -lR
- name: Add Linux sources to GtiHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-firmware:
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
- name: Display structure of downloaded files
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v5
with:
name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
merge-multiple: true
path: ./elfs
- name: Zip debug elfs
run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
# For diagnostics
- name: Display structure of downloaded files
run: ls -lR
- name: Add bins and debug elfs to GitHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.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, version]
env:
targets: |-
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Publish firmware to meshtastic.github.io
uses: peaceiris/actions-gh-pages@v4
env:
# On event/* branches, use the event name as the destination prefix
DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
with:
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
external_repository: meshtastic/meshtastic.github.io
publish_branch: master
publish_dir: ./publish
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: ${{ needs.version.outputs.long }}
enable_jekyll: true

View File

@@ -1,395 +0,0 @@
name: Build One Target
on:
workflow_dispatch:
inputs:
arch:
type: choice
options:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- native
target:
type: string
required: false
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
# find-target:
# type: boolean
# default: true
# description: 'Find the available targets'
jobs:
find-targets:
if: ${{ inputs.target == '' }}
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
run: |
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra)
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "Targets:" >> $GITHUB_STEP_SUMMARY
echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY
version:
if: ${{ inputs.target != '' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- 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 }}
build-arch:
if: ${{ inputs.target != '' && inputs.arch != 'native' }}
needs: [version]
strategy:
fail-fast: false
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ inputs.target }}
platform: ${{ inputs.arch }}
build-debian-src:
if: ${{ github.repository == 'meshtastic/firmware' && inputs.arch == 'native' }}
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
build_location: local
secrets: inherit
package-pio-deps-native-tft:
if: ${{ inputs.arch == 'native' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
secrets: inherit
test-native:
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }}
uses: ./.github/workflows/test_native.yml
docker-deb-amd64:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-deb-amd64-tft:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
pio_env: native-tft
docker-alp-amd64:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-alp-amd64-tft:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
pio_env: native-tft
docker-deb-arm64:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm64
runs-on: ubuntu-24.04-arm
push: false
docker-deb-armv7:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm/v7
runs-on: ubuntu-24.04-arm
push: false
gather-artifacts:
permissions:
contents: write
pull-requests: write
strategy:
fail-fast: false
runs-on: ubuntu-latest
needs: [version, build-arch]
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v5
with:
path: ./
pattern: firmware-*-*
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- 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-${{inputs.target}}-${{ needs.version.outputs.long }}
overwrite: true
path: |
./firmware-*.bin
./firmware-*.uf2
./firmware-*.hex
./firmware-*-ota.zip
./device-*.sh
./device-*.bat
./littlefs-*.bin
./bleota*bin
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v5
with:
pattern: firmware-*-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
# For diagnostics
- name: Show artifacts
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v4
with:
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
overwrite: true
path: ./*.elf
retention-days: 30
- uses: scruplelesswizard/comment-artifact@main
if: ${{ github.event_name == 'pull_request' }}
with:
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}
release-artifacts:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
- version
- gather-artifacts
- build-debian-src
- package-pio-deps-native-tft
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Create release
uses: softprops/action-gh-release@v2
id: create_release
with:
draft: true
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
uses: actions/download-artifact@v5
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v5
with:
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-${{ 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
run: ls -lR
- name: Add Linux sources to GtiHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-firmware:
strategy:
fail-fast: false
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-*-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
- name: Display structure of downloaded files
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v5
with:
pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip
merge-multiple: true
path: ./elfs
- name: Zip debug elfs
run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs
# For diagnostics
- name: Display structure of downloaded files
run: ls -lR
- name: Add bins and debug elfs to GitHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-firmware:
runs-on: ubuntu-24.04
if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }}
needs: [release-firmware, version]
env:
targets: |-
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Publish firmware to meshtastic.github.io
uses: peaceiris/actions-gh-pages@v4
env:
# On event/* branches, use the event name as the destination prefix
DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
with:
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
external_repository: meshtastic/meshtastic.github.io
publish_branch: master
publish_dir: ./publish
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: ${{ needs.version.outputs.long }}
enable_jekyll: true

View File

@@ -21,20 +21,18 @@ permissions:
jobs:
docker-multiarch:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_manifest.yml
with:
release_channel: daily
secrets: inherit
package-ppa:
if: github.repository == 'meshtastic/firmware'
strategy:
fail-fast: false
matrix:
series:
- jammy # 22.04 LTS
- noble # 24.04 LTS
- jammy # 22.04
- noble # 24.04
- plucky # 25.04
- questing # 25.10
uses: ./.github/workflows/package_ppa.yml
@@ -44,7 +42,6 @@ jobs:
secrets: inherit
package-obs:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/package_obs.yml
with:
obs_project: network:Meshtastic:daily
@@ -52,7 +49,6 @@ jobs:
secrets: inherit
hook-copr:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/hook_copr.yml
with:
copr_project: daily

View File

@@ -3,7 +3,7 @@ concurrency:
group: ci-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
on:
# # Triggers the workflow on push but only for the main branches
# # Triggers the workflow on push but only for the master branch
push:
branches:
- master
@@ -19,7 +19,6 @@ on:
- master
- develop
- event/*
- self-hosted-testing
paths-ignore:
- "**.md"
#- "**.yml"
@@ -28,7 +27,6 @@ on:
jobs:
setup:
if: github.repository == 'meshtastic/firmware'
strategy:
fail-fast: false
matrix:
@@ -42,7 +40,7 @@ jobs:
- rp2350
- stm32
- check
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
@@ -76,7 +74,6 @@ jobs:
check: ${{ steps.jsonStep.outputs.check }}
version:
if: github.repository == 'meshtastic/firmware'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
@@ -98,7 +95,7 @@ jobs:
matrix: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v5
- name: Build base
@@ -211,11 +208,10 @@ jobs:
secrets: inherit
test-native:
if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }}
if: ${{ !contains(github.ref_name, 'event/') }}
uses: ./.github/workflows/test_native.yml
docker-deb-amd64:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
@@ -224,7 +220,6 @@ jobs:
push: false
docker-deb-amd64-tft:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
@@ -234,7 +229,6 @@ jobs:
pio_env: native-tft
docker-alp-amd64:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
@@ -243,7 +237,6 @@ jobs:
push: false
docker-alp-amd64-tft:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
@@ -253,7 +246,6 @@ jobs:
pio_env: native-tft
docker-deb-arm64:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
@@ -262,7 +254,6 @@ jobs:
push: false
docker-deb-armv7:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
@@ -271,8 +262,6 @@ jobs:
push: false
gather-artifacts:
# trunk-ignore(checkov/CKV2_GHA_1)
if: github.repository == 'meshtastic/firmware'
permissions:
contents: write
pull-requests: write
@@ -372,7 +361,7 @@ jobs:
release-artifacts:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
if: ${{ github.event_name == 'workflow_dispatch' }}
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
@@ -447,7 +436,7 @@ jobs:
- rp2350
- stm32
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware'}}
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-artifacts, version]
steps:
- name: Checkout

View File

@@ -1,508 +0,0 @@
name: Merge Queue
# Not sure how concurrency works in merge_queue, removing for now.
# concurrency:
# group: merge-queue-${{ github.head_ref || github.run_id }}
# cancel-in-progress: true
on:
# Merge group is a special trigger that is used to trigger the workflow when a merge group is created.
merge_group:
env:
FAIL_FAST_PER_ARCH: true
jobs:
setup:
strategy:
fail-fast: true
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
run: |
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
else
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
outputs:
esp32: ${{ steps.jsonStep.outputs.esp32 }}
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
stm32: ${{ steps.jsonStep.outputs.stm32 }}
check: ${{ steps.jsonStep.outputs.check }}
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- 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:
fail-fast: true
matrix: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v5
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Check ${{ matrix.board }}
run: bin/check-all.sh ${{ matrix.board }}
build-esp32:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32
build-esp32s3:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32s3
build-esp32c3:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c3
build-esp32c6:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c6
build-nrf52840:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: nrf52840
build-rp2040:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2040
build-rp2350:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2350
build-stm32:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: stm32
build-debian-src:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
build_location: local
secrets: inherit
package-pio-deps-native-tft:
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
secrets: inherit
test-native:
if: ${{ !contains(github.ref_name, 'event/') }}
uses: ./.github/workflows/test_native.yml
docker-deb-amd64:
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-deb-amd64-tft:
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
pio_env: native-tft
docker-alp-amd64:
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-alp-amd64-tft:
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
pio_env: native-tft
docker-deb-arm64:
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm64
runs-on: ubuntu-24.04-arm
push: false
docker-deb-armv7:
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm/v7
runs-on: ubuntu-24.04-arm
push: false
gather-artifacts:
# trunk-ignore(checkov/CKV2_GHA_1)
permissions:
contents: write
pull-requests: write
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest
needs:
[
version,
build-esp32,
build-esp32s3,
build-esp32c3,
build-esp32c6,
build-nrf52840,
build-rp2040,
build-rp2350,
build-stm32,
]
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v5
with:
path: ./
pattern: firmware-${{matrix.arch}}-*
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- name: Move files up
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v4
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
path: |
./firmware-*.bin
./firmware-*.uf2
./firmware-*.hex
./firmware-*-ota.zip
./device-*.sh
./device-*.bat
./littlefs-*.bin
./bleota*bin
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v5
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
# For diagnostics
- name: Show artifacts
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
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}}-${{ needs.version.outputs.long }}.zip
overwrite: true
path: ./*.elf
retention-days: 30
- uses: scruplelesswizard/comment-artifact@main
if: ${{ github.event_name == 'pull_request' }}
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}
release-artifacts:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
- version
- gather-artifacts
- build-debian-src
- package-pio-deps-native-tft
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Create release
uses: softprops/action-gh-release@v2
id: create_release
with:
draft: true
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
uses: actions/download-artifact@v5
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v5
with:
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-${{ 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
run: ls -lR
- name: Add Linux sources to GtiHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-firmware:
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
- name: Display structure of downloaded files
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v5
with:
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}}-${{ needs.version.outputs.long }}.zip ./elfs
# For diagnostics
- name: Display structure of downloaded files
run: ls -lR
- name: Add bins and debug elfs to GitHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-firmware:
runs-on: ubuntu-24.04
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-firmware, version]
env:
targets: |-
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Publish firmware to meshtastic.github.io
uses: peaceiris/actions-gh-pages@v4
env:
# On event/* branches, use the event name as the destination prefix
DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
with:
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
external_repository: meshtastic/meshtastic.github.io
publish_branch: master
publish_dir: ./publish
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: ${{ needs.version.outputs.long }}
enable_jekyll: true

View File

@@ -10,7 +10,7 @@ permissions:
jobs:
check-label:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Check for PR labels
uses: actions/github-script@v8

View File

@@ -21,10 +21,10 @@ jobs:
fail-fast: false
matrix:
series:
- jammy # 22.04 LTS
- noble # 24.04 LTS
- jammy # 22.04
- noble # 24.04
- plucky # 25.04
- questing # 25.10
# - questing # 25.10
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: |-

View File

@@ -8,15 +8,15 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.471
- renovate@41.115.6
- checkov@3.2.469
- renovate@41.94.0
- prettier@3.6.2
- trufflehog@3.90.6
- trufflehog@3.90.5
- yamllint@1.37.1
- bandit@1.8.6
- trivy@0.66.0
- taplo@0.10.0
- ruff@0.13.0
- ruff@0.12.11
- isort@6.0.1
- markdownlint@0.45.0
- oxipng@9.1.5

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env bash
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
export PIP_BREAK_SYSTEM_PACKAGES=1
if (echo $2 | grep -q "esp32"); then

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
BPS_RESET=false

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
CHANGE_MODE=false

View File

@@ -87,9 +87,6 @@
</screenshots>
<releases>
<release version="2.7.10" date="2025-09-18">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.10</url>
</release>
<release version="2.7.9" date="2025-09-03">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9</url>
</release>

View File

@@ -6,8 +6,6 @@ from os.path import join
import subprocess
import json
import re
import time
from datetime import datetime
from readprops import readProps
@@ -127,16 +125,11 @@ for pref in userPrefs:
pref_flags.append("-D" + pref + "=" + env.StringifyMacro(userPrefs[pref]) + "")
# General options that are passed to the C and C++ compilers
# Calculate unix epoch for current day (midnight)
current_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
build_epoch = int(current_date.timestamp())
flags = [
"-DAPP_VERSION=" + verObj["long"],
"-DAPP_VERSION_SHORT=" + verObj["short"],
"-DAPP_ENV=" + env.get("PIOENV"),
"-DAPP_REPO=" + repo_owner,
"-DBUILD_EPOCH=" + str(build_epoch),
] + pref_flags
print ("Using flags:")

View File

@@ -1,43 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default_16MB.csv",
"memory_type": "qio_qspi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "qspi",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "heltec_v4"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "heltec_wifi_lora_32 v4 (16 MB FLASH, 2 MB PSRAM)",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 2097152,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://heltec.org/",
"vendor": "heltec"
}

13
debian/changelog vendored
View File

@@ -1,7 +1,11 @@
meshtasticd (2.7.10.0) UNRELEASED; urgency=medium
meshtasticd (2.7.9.0) UNRELEASED; urgency=medium
[ Austin Lane ]
* Initial packaging
* Version 2.5.19
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
@@ -43,7 +47,4 @@ meshtasticd (2.7.10.0) UNRELEASED; urgency=medium
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Thu, 18 Sep 2025 22:11:37 +0000
-- <github-actions[bot]@users.noreply.github.com> Wed, 03 Sep 2025 23:39:17 +0000

View File

@@ -1,8 +1,7 @@
#!/usr/bin/bash
export DEBFULLNAME="GitHub Actions"
export DEBEMAIL="github-actions[bot]@users.noreply.github.com"
PKG_VERSION=$(python3 bin/buildinfo.py short)
dch --newversion "$PKG_VERSION.0" \
--distribution unstable \
"Version $PKG_VERSION"
--distribution UNRELEASED \
"GitHub Actions Automatic version bump"

View File

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

View File

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

View File

@@ -53,15 +53,14 @@ build_flags = -Wno-missing-field-initializers
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
#-DBUILD_EPOCH=$UNIX_TIME
#-D OLED_PL=1
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
monitor_speed = 115200
monitor_filters = direct
lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/9573abb64dc9c94f3051348f2bf4fc5cedf03c22.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
@@ -114,18 +113,18 @@ lib_deps =
[radiolib_base]
lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
jgromes/RadioLib@7.3.0
jgromes/RadioLib@7.2.1
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip
https://github.com/meshtastic/device-ui/archive/233d18ef42e9d189f90fdfe621f0cd7edff2d221.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
adafruit/Adafruit BusIO@1.17.3
adafruit/Adafruit BusIO@1.17.2
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library

View File

@@ -183,9 +183,9 @@ class AmbientLightingThread : public concurrency::OSThread
#endif
#endif
pixels.show();
// LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
// moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
// moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);

View File

@@ -2,12 +2,6 @@
#include "configuration.h"
// Forward declarations
#if defined(DEBUG_HEAP)
class MemGet;
extern MemGet memGet;
#endif
// DEBUG LED
#ifndef LED_STATE_ON
#define LED_STATE_ON 1
@@ -29,7 +23,6 @@ extern MemGet memGet;
#define MESHTASTIC_LOG_LEVEL_ERROR "ERROR"
#define MESHTASTIC_LOG_LEVEL_CRIT "CRIT "
#define MESHTASTIC_LOG_LEVEL_TRACE "TRACE"
#define MESHTASTIC_LOG_LEVEL_HEAP "HEAP"
#include "SerialConsole.h"
@@ -69,25 +62,6 @@ extern MemGet memGet;
#endif
#endif
#if defined(DEBUG_HEAP)
#define LOG_HEAP(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__)
// Macro-based heap debugging
#define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap();
#define DEBUG_HEAP_AFTER(context, ptr) \
do { \
auto heapAfter = memGet.getFreeHeap(); \
if (heapBefore != heapAfter) { \
LOG_HEAP("Alloc in %s pointer 0x%x, size: %u, free: %u", context, ptr, heapBefore - heapAfter, heapAfter); \
} \
} while (0)
#else
#define LOG_HEAP(...)
#define DEBUG_HEAP_BEFORE
#define DEBUG_HEAP_AFTER(context, ptr)
#endif
/// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic
extern "C" void logLegacy(const char *level, const char *fmt, ...);

View File

@@ -52,9 +52,6 @@ const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role
case meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN:
return "Client Hidden";
break;
case meshtastic_Config_DeviceConfig_Role_CLIENT_BASE:
return "Client Base";
break;
case meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND:
return "Lost and Found";
break;

View File

@@ -6,5 +6,7 @@ class DisplayFormatters
public:
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
bool usePreset);
public:
static const char *getDeviceRole(meshtastic_Config_DeviceConfig_Role role);
};

View File

@@ -851,9 +851,9 @@ void Power::readPowerStatus()
running++;
}
}
LOG_HEAP(threadlist);
LOG_HEAP("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
LOG_DEBUG(threadlist);
LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
lastheap = memGet.getFreeHeap();
}
#ifdef DEBUG_HEAP_MQTT

View File

@@ -4,7 +4,6 @@
#include "concurrency/OSThread.h"
#include "configuration.h"
#include "main.h"
#include "memGet.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include <assert.h>
#include <cstring>
@@ -167,16 +166,6 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
print(thread->ThreadName);
print("] ");
}
#ifdef DEBUG_HEAP
// Add heap free space bytes prefix before every log message
#ifdef ARCH_PORTDUINO
::printf("[heap %u] ", memGet.getFreeHeap());
#else
printf("[heap %u] ", memGet.getFreeHeap());
#endif
#endif // DEBUG_HEAP
r += vprintf(logLevel, format, arg);
}

View File

@@ -86,9 +86,9 @@ void OSThread::run()
#ifdef DEBUG_HEAP
auto newHeap = memGet.getFreeHeap();
if (newHeap < heap)
LOG_HEAP("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap);
LOG_DEBUG("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap);
if (heap < newHeap)
LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
LOG_DEBUG("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
#endif
runned();

View File

@@ -262,13 +262,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define VEXT_ON_VALUE LOW
#endif
// -----------------------------------------------------------------------------
// Rotary encoder
// -----------------------------------------------------------------------------
#ifndef ROTARY_DELAY
#define ROTARY_DELAY 5
#endif
// -----------------------------------------------------------------------------
// GPS
// -----------------------------------------------------------------------------
@@ -438,6 +431,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_SERIAL 1
#define MESHTASTIC_EXCLUDE_POWERSTRESS 1
#define MESHTASTIC_EXCLUDE_ADMIN 1
#define MESHTASTIC_EXCLUDE_AMBIENTLIGHTING 1
#endif
// // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled)

View File

@@ -294,7 +294,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = AHT10;
break;
#endif
#if !defined(M5STACK_UNITC6L)
case INA_ADDR:
case INA_ADDR_ALTERNATE:
case INA_ADDR_WAVESHARE_UPS:
@@ -341,7 +340,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
// else: probably a RAK12500/UBLOX GPS on I2C
}
break;
#endif
case MCP9808_ADDR:
// We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some
// weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips.

View File

@@ -1,4 +1,5 @@
#include <cstring> // Include for strstr
#include <string>
#include <vector>
#include "configuration.h"
@@ -516,7 +517,6 @@ bool GPS::setup()
}
}
// Rare Serial Speeds
#ifndef CONFIG_IDF_TARGET_ESP32C6
if (probeTries == GPS_PROBETRIES) {
LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]);
gnssModel = probe(rareSerialSpeeds[speedSelect]);
@@ -527,7 +527,6 @@ bool GPS::setup()
}
}
}
#endif
}
if (gnssModel != GNSS_MODEL_UNKNOWN) {
@@ -1215,7 +1214,7 @@ static const char *DETECTED_MESSAGE = "%s detected";
LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \
clearBuffer(); \
_serial_gps->write(COMMAND "\r\n"); \
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP, serialSpeed); \
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP); \
if (detectedDriver != GNSS_MODEL_UNKNOWN) { \
return detectedDriver; \
} \
@@ -1383,55 +1382,36 @@ GnssModel_t GPS::probe(int serialSpeed)
return GNSS_MODEL_UNKNOWN;
}
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap, int serialSpeed)
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap)
{
// Calculate buffer size based on baud rate - 256 bytes for 9600 baud as baseline
// Higher baud rates get proportionally larger buffers to handle more data
int bufferSize = (serialSpeed * 256) / 9600;
// Clamp buffer size between reasonable limits
if (bufferSize < 128)
bufferSize = 128;
if (bufferSize > 2048)
bufferSize = 2048;
char *response = new char[bufferSize](); // Dynamically allocate based on baud rate
uint16_t responseLen = 0;
String response = "";
unsigned long start = millis();
while (millis() - start < timeout) {
if (_serial_gps->available()) {
char c = _serial_gps->read();
response += (char)_serial_gps->read();
// Add char to buffer if there's space
if (responseLen < bufferSize - 1) {
response[responseLen++] = c;
response[responseLen] = '\0';
}
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
if (response.endsWith(",") || response.endsWith("\r\n")) {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
LOG_DEBUG(response.c_str());
#endif
// check if we can see our chips
for (const auto &chipInfo : responseMap) {
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
if (strstr(response.c_str(), chipInfo.detectionString.c_str()) != nullptr) {
LOG_INFO("%s detected", chipInfo.chipName.c_str());
delete[] response; // Cleanup before return
return chipInfo.driver;
}
}
}
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
// Reset the response buffer for the next potential message
responseLen = 0;
response[0] = '\0';
if (response.endsWith("\r\n")) {
response.trim();
response = ""; // Reset the response string for the next potential message
}
}
}
#ifdef GPS_DEBUG
LOG_DEBUG(response);
LOG_DEBUG(response.c_str());
#endif
delete[] response; // Cleanup before return
return GNSS_MODEL_UNKNOWN; // Return unknown on timeout
return GNSS_MODEL_UNKNOWN; // Return empty string on timeout
}
GPS *GPS::createGps()

View File

@@ -237,7 +237,7 @@ class GPS : private concurrency::OSThread
virtual int32_t runOnce() override;
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap, int serialSpeed);
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap);
// Get GNSS model
GnssModel_t probe(int serialSpeed);

View File

@@ -9,9 +9,6 @@
static RTCQuality currentQuality = RTCQualityNone;
uint32_t lastSetFromPhoneNtpOrGps = 0;
static uint32_t lastTimeValidationWarning = 0;
static const uint32_t TIME_VALIDATION_WARNING_INTERVAL_MS = 15000; // 15 seconds
RTCQuality getRTCQuality()
{
return currentQuality;
@@ -51,9 +48,7 @@ RTCSetResult readFromRTC()
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
}
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime;
}
#endif
@@ -92,10 +87,7 @@ RTCSetResult readFromRTC()
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime;
}
#endif
@@ -138,20 +130,15 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
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) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
#ifdef GPS_DEBUG
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
#endif
return RTCSetResultInvalidTime;
} else if ((uint64_t)tv->tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
// Calculate max allowed time safely to avoid overflow in logging
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis();
}
} else if (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
#ifdef GPS_DEBUG
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
BUILD_EPOCH + FORTY_YEARS);
#endif
return RTCSetResultInvalidTime;
}
#endif
@@ -269,20 +256,15 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
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) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
#ifdef GPS_DEBUG
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
#endif
return RTCSetResultInvalidTime;
} else if ((uint64_t)tv.tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
// Calculate max allowed time safely to avoid overflow in logging
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis();
}
} else if (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
#ifdef GPS_DEBUG
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
BUILD_EPOCH + FORTY_YEARS);
#endif
return RTCSetResultInvalidTime;
}
#endif
@@ -342,40 +324,14 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
time_t gm_mktime(struct tm *tm)
{
#if !MESHTASTIC_EXCLUDE_TZ
time_t result = 0;
// First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
int year = 1900 + tm->tm_year; // tm_year is years since 1900
int year_minus_one = year - 1;
int days_before_this_year = 0;
days_before_this_year += year_minus_one * 365;
// leap days: every 4 years, except 100s, but including 400s.
days_before_this_year += year_minus_one / 4 - year_minus_one / 100 + year_minus_one / 400;
// subtract from 1970-01-01 to get days since epoch
days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400);
// Now, within this tm->year, compute the days *before* this tm->month starts.
int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11
// If this is a leap year, and we're past February, add a day:
if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
days_this_year_before_this_month += 1;
setenv("TZ", "GMT0", 1);
time_t res = mktime(tm);
if (*config.device.tzdef) {
setenv("TZ", config.device.tzdef, 1);
} else {
setenv("TZ", "UTC0", 1);
}
// And within this month:
int days_this_month_before_today = tm->tm_mday - 1; // tm->tm_mday is 1..31
// Now combine them all together, and convert days to seconds:
result += (days_before_this_year + days_this_year_before_this_month + days_this_month_before_today);
result *= 86400L;
// Finally, add in the hours, minutes, and seconds of today:
result += tm->tm_hour * 3600;
result += tm->tm_min * 60;
result += tm->tm_sec;
return result;
return res;
#else
return mktime(tm);
#endif

View File

@@ -56,5 +56,5 @@ time_t gm_mktime(struct tm *tm);
#define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60
#ifdef BUILD_EPOCH
static constexpr uint64_t FORTY_YEARS = (40ULL * 365 * SEC_PER_DAY); // Use 64-bit arithmetic to prevent overflow
#define FORTY_YEARS (40UL * 365 * SEC_PER_DAY) // probably time to update your firmware
#endif

View File

@@ -25,7 +25,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "PowerMon.h"
#include "Throttle.h"
#include "configuration.h"
#include "meshUtils.h"
#if HAS_SCREEN
#include <OLEDDisplay.h>
@@ -59,6 +58,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "mesh-pb-constants.h"
#include "mesh/Channels.h"
#include "mesh/generated/meshtastic/deviceonly.pb.h"
#include "meshUtils.h"
#include "modules/ExternalNotificationModule.h"
#include "modules/TextMessageModule.h"
#include "modules/WaypointModule.h"
@@ -149,6 +149,11 @@ void Screen::showSimpleBanner(const char *message, uint32_t durationMs)
// Called to trigger a banner with custom message and duration
void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
{
// Don't show overlay banner if virtual keyboard is active
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
return;
}
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
#endif
@@ -173,6 +178,11 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
// Called to trigger a banner with custom message and duration
void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback)
{
// Don't show node picker if virtual keyboard is active
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
return;
}
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
#endif
@@ -196,6 +206,11 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits,
std::function<void(uint32_t)> bannerCallback)
{
// Don't show number picker if virtual keyboard is active
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
return;
}
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
#endif
@@ -221,24 +236,10 @@ void Screen::showTextInput(const char *header, const char *initialText, uint32_t
{
LOG_INFO("showTextInput called with header='%s', durationMs=%d", header ? header : "NULL", durationMs);
if (NotificationRenderer::virtualKeyboard) {
delete NotificationRenderer::virtualKeyboard;
NotificationRenderer::virtualKeyboard = nullptr;
}
NotificationRenderer::textInputCallback = nullptr;
NotificationRenderer::virtualKeyboard = new VirtualKeyboard();
if (header) {
NotificationRenderer::virtualKeyboard->setHeader(header);
}
if (initialText) {
NotificationRenderer::virtualKeyboard->setInputText(initialText);
}
// Set up callback with safer cleanup mechanism
// Start OnScreenKeyboardModule session (non-touch variant)
OnScreenKeyboardModule::instance().start(header, initialText, durationMs, textCallback);
NotificationRenderer::virtualKeyboard = OnScreenKeyboardModule::instance().getKeyboard();
NotificationRenderer::textInputCallback = textCallback;
NotificationRenderer::virtualKeyboard->setCallback([textCallback](const std::string &text) { textCallback(text); });
// Store the message and set the expiration timestamp (use same pattern as other notifications)
strncpy(NotificationRenderer::alertBannerMessage, header ? header : "Text Input", 255);
@@ -355,14 +356,6 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_SPISSD1306)
dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48);
if (!dispdev->init()) {
LOG_DEBUG("Error: SSD1306 not detected!");
} else {
static_cast<SSD1306Spi *>(dispdev)->setHorizontalOffset(32);
LOG_INFO("SSD1306 init success");
}
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
@@ -553,7 +546,7 @@ void Screen::setup()
// === Apply loaded brightness ===
#if defined(ST7789_CS)
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306)
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
dispdev->setBrightness(brightness);
#endif
LOG_INFO("Applied screen brightness: %d", brightness);
@@ -600,7 +593,7 @@ void Screen::setup()
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
#elif defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
#elif !defined(M5STACK_UNITC6L)
#else
dispdev->flipScreenVertically();
#endif
}
@@ -738,11 +731,7 @@ int32_t Screen::runOnce()
#ifndef DISABLE_WELCOME_UNSET
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
#if defined(M5STACK_UNITC6L)
menuHandler::LoraRegionPicker();
#else
menuHandler::OnboardMessage();
#endif
}
#endif
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
@@ -955,12 +944,8 @@ void Screen::setFrames(FrameFocus focus)
#if defined(DISPLAY_CLOCK_FRAME)
if (!hiddenFrames.clock) {
fsi.positions.clock = numframes;
#if defined(M5STACK_UNITC6L)
normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
#else
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
: graphics::ClockRenderer::drawDigitalClockFrame;
#endif
indicatorIcons.push_back(digital_icon_clock);
}
#endif
@@ -1037,7 +1022,7 @@ void Screen::setFrames(FrameFocus focus)
if (!hiddenFrames.chirpy) {
fsi.positions.chirpy = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy;
indicatorIcons.push_back(chirpy_small);
indicatorIcons.push_back(small_chirpy);
}
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
@@ -1299,8 +1284,7 @@ void Screen::blink()
delay(50);
count = count - 1;
}
// The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in
// OLEDDisplay.
// The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in OLEDDisplay.
dispdev->setBrightness(brightness);
}
@@ -1388,10 +1372,6 @@ void Screen::handleShowNextFrame()
void Screen::setFastFramerate()
{
#if defined(M5STACK_UNITC6L)
dispdev->clear();
dispdev->display();
#endif
// We are about to start a transition so speed up fps
targetFramerate = SCREEN_TRANSITION_FRAMERATE;
@@ -1431,21 +1411,28 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
// Incoming message
devicestate.has_rx_text_message = true; // Needed to include the message frame
hasUnreadMessage = true; // Enables mail icon in the header
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
// Only wake/force display if the configuration allows it
if (shouldWakeOnReceivedMessage()) {
setOn(true); // Wake up the screen first
forceDisplay(); // Forces screen redraw
// Always update frame list to include new message, but defer UI updates if virtual keyboard is active
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
// Only wake/force display if the configuration allows it
if (shouldWakeOnReceivedMessage()) {
setOn(true); // Wake up the screen first
forceDisplay(); // Forces screen redraw
}
} else {
// Virtual keyboard is active - just mark that frames need regeneration when keyboard closes
// The devicestate and hasUnreadMessage are already set above, so message will appear later
LOG_DEBUG("Virtual keyboard active - deferring frame list update for new message");
}
// === Prepare banner content ===
// Show message alert - either as normal banner or as keyboard popup
// === Common variables ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
char banner[256];
// Check for bell character in message to determine alert type
bool isAlert = false;
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
@@ -1455,31 +1442,57 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
}
}
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
// === Normal banner mode ===
char banner[256];
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
}
} else {
strcpy(banner, "Alert Received");
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
} else {
strcpy(banner, "New Message");
}
}
screen->showSimpleBanner(banner, 3000);
} else {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
#endif
// === Virtual keyboard popup mode ===
char title[64];
if (isAlert) {
if (longName && longName[0]) {
snprintf(title, sizeof(title), "Alert from %s", longName);
} else {
strcpy(title, "Alert Received");
}
} else {
strcpy(banner, "New Message");
if (longName && longName[0]) {
snprintf(title, sizeof(title), "%s", longName);
} else {
strcpy(title, "New Message");
}
}
// Prepare content - clean the message content
char content[200];
size_t contentLen = 0;
for (size_t i = 0; i < packet->decoded.payload.size && i < sizeof(content) - 1; i++) {
if (msgRaw[i] != '\x07' && msgRaw[i] != '\0') { // Skip bell character and null
content[contentLen++] = msgRaw[i];
}
}
content[contentLen] = '\0';
// Show popup with title and content on virtual keyboard
NotificationRenderer::showKeyboardMessagePopupWithTitle(title, content, 5000);
// Force display update to show the popup immediately
setFastFramerate();
forceDisplay();
}
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
playLongBeep();
#else
screen->showSimpleBanner(banner, 3000);
#endif
}
}
@@ -1577,11 +1590,7 @@ int Screen::handleInputEvent(const InputEvent *event)
if (devicestate.rx_text_message.from) {
menuHandler::messageResponseMenu();
} else {
#if defined(M5STACK_UNITC6L)
menuHandler::textMessageMenu();
#else
menuHandler::textMessageBaseMenu();
#endif
}
} else if (framesetInfo.positions.firstFavorite != 255 &&
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
@@ -1640,15 +1649,13 @@ bool shouldWakeOnReceivedMessage()
/*
The goal here is to determine when we do NOT wake up the screen on message received:
- Any ext. notifications are turned on
- If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE
- If role is not client / client_mute
- If the battery level is very low
*/
if (moduleConfig.external_notification.enabled) {
return false;
}
if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT,
meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN,
meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
return false;
}
if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {

View File

@@ -81,8 +81,6 @@ class Screen
#include <SSD1306Wire.h>
#elif defined(USE_ST7789)
#include <ST7789Spi.h>
#elif defined(USE_SPISSD1306)
#include <SSD1306Spi.h>
#else
// the SH1106/SSD1306 variant is auto-detected
#include <AutoOLEDWire.h>

View File

@@ -79,10 +79,6 @@
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#elif defined(M5STACK_UNITC6L)
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
#define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13
#define FONT_LARGE FONT_SMALL_LOCAL // Height: 13
#else
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
#define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19

View File

@@ -129,7 +129,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1;
#if !defined(M5STACK_UNITC6L)
// === Battery Icons ===
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
batteryX += 1;
@@ -368,7 +368,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
}
}
}
#endif
display->setColor(WHITE); // Reset for other UI
}

View File

@@ -2,13 +2,11 @@
#if HAS_SCREEN
#include "ClockRenderer.h"
#include "NodeDB.h"
#include "UIRenderer.h"
#include "configuration.h"
#include "gps/GeoCoord.h"
#include "gps/RTC.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/draw/UIRenderer.h"
#include "graphics/emotes.h"
#include "graphics/images.h"
#include "main.h"
@@ -192,7 +190,6 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
const char *titleStr = "";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
int line = 0;
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
@@ -317,7 +314,6 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
const char *titleStr = "";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
int line = 0;
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {

View File

@@ -277,13 +277,12 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
// Line 1 (Still)
#if !defined(M5STACK_UNITC6L)
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
if (config.display.heading_bold)
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
display->setColor(WHITE);
#endif
// Setup string to assemble analogClock string
std::string analogClock = "";
@@ -330,7 +329,8 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
#if HAS_GPS
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
// Line 3
if (uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
if (config.display.gps_format !=
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
UIRenderer::drawGpsAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
// Line 4
@@ -386,11 +386,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
char shortnameble[35];
getMacAddr(dmac);
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
#if defined(M5STACK_UNITC6L)
snprintf(shortnameble, sizeof(shortnameble), "%s", screen->ourId);
#else
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
#endif
int textWidth = display->getStringWidth(shortnameble);
int nameX = (SCREEN_WIDTH - textWidth);
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
@@ -409,11 +405,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
char regionradiopreset[25];
const char *region = myRegion ? myRegion->name : NULL;
if (region != nullptr) {
#if defined(M5STACK_UNITC6L)
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region);
#else
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
#endif
snprintf(regionradiopreset, sizeof(regionradiopreset), "Reg: %s/%s", region, mode);
}
textWidth = display->getStringWidth(regionradiopreset);
nameX = (SCREEN_WIDTH - textWidth) / 2;
@@ -425,17 +417,9 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
float freq = RadioLibInterface::instance->getFreq();
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
if (config.lora.channel_num == 0) {
#if defined(M5STACK_UNITC6L)
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz", freqStr);
#else
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
#endif
} else {
#if defined(M5STACK_UNITC6L)
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num);
#else
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
#endif
}
size_t len = strlen(frequencyslot);
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
@@ -445,7 +429,6 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
#if !defined(M5STACK_UNITC6L)
// === Fifth Row: Channel Utilization ===
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
@@ -502,7 +485,6 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
chUtilPercentage);
#endif
}
// ****************************
@@ -528,11 +510,8 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
#ifdef USE_EINK
barsOffset -= 12;
#endif
#if defined(M5STACK_UNITC6L)
const int barX = x + 45 + barsOffset;
#else
const int barX = x + 40 + barsOffset;
#endif
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
if (total == 0)
return;
@@ -557,7 +536,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
// Label
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(labelX, getTextPositions(display)[line], label);
#if !defined(M5STACK_UNITC6L)
// Bar
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
display->setColor(WHITE);
@@ -565,7 +544,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
display->fillRect(barX, barY, fillWidth, barHeight);
display->setColor(WHITE);
#endif
// Value string
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
@@ -618,16 +597,10 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
line += 1;
}
line += 1;
char appversionstr[35];
snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION));
char appversionstr_formatted[40];
char *lastDot = strrchr(appversionstr, '.');
#if defined(M5STACK_UNITC6L)
if (lastDot != nullptr) {
*lastDot = '\0'; // truncate string
}
#else
if (lastDot) {
size_t prefixLen = lastDot - appversionstr;
strncpy(appversionstr_formatted, appversionstr, prefixLen);
@@ -638,12 +611,10 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
appversionstr[sizeof(appversionstr) - 1] = '\0';
}
#endif
int textWidth = display->getStringWidth(appversionstr);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
#if !defined(M5STACK_UNITC6L)
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
line += 1;
char uptimeStr[32] = "";
@@ -662,7 +633,6 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
}
#endif
}
// ****************************
@@ -691,7 +661,6 @@ void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int1
textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("World!") / 2);
display->drawString(textX, getTextPositions(display)[line++], "World!");
}
} // namespace DebugRenderer
} // namespace graphics
#endif

View File

@@ -33,6 +33,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
// System screen display
void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// Chirpy screen display
void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
} // namespace DebugRenderer

View File

@@ -31,21 +31,19 @@ uint8_t test_count = 0;
void menuHandler::loraMenu()
{
static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "LoRa Region"};
enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, lora_picker = 3 };
static const char *optionsArray[] = {"Back", "Region Picker", "Device Role"};
enum optionsNumbers { Back = 0, lora_picker = 1, device_role_picker = 2 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "LoRa Actions";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
// No action
} else if (selected == device_role_picker) {
menuHandler::menuQueue = menuHandler::device_role_picker;
} else if (selected == radio_preset_picker) {
menuHandler::menuQueue = menuHandler::radio_preset_picker;
} else if (selected == lora_picker) {
menuHandler::menuQueue = menuHandler::lora_picker;
} else if (selected == device_role_picker) {
menuHandler::menuQueue = menuHandler::device_role_picker;
}
};
screen->showOverlayBanner(bannerOptions);
@@ -104,11 +102,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
"NP_865",
"BR_902"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "LoRa Region";
#else
bannerOptions.message = "Set the LoRa region";
#endif
bannerOptions.durationMs = duration;
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 27;
@@ -182,53 +176,6 @@ void menuHandler::DeviceRolePicker()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::RadioPresetPicker()
{
static const char *optionsArray[] = {"Back", "LongSlow", "LongModerate", "LongFast", "MediumSlow",
"MediumFast", "ShortSlow", "ShortFast", "ShortTurbo"};
enum optionsNumbers {
Back = 0,
radiopreset_LongSlow = 1,
radiopreset_LongModerate = 2,
radiopreset_LongFast = 3,
radiopreset_MediumSlow = 4,
radiopreset_MediumFast = 5,
radiopreset_ShortSlow = 6,
radiopreset_ShortFast = 7,
radiopreset_ShortTurbo = 8
};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Radio Preset";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 9;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
menuHandler::menuQueue = menuHandler::lora_Menu;
screen->runNow();
return;
} else if (selected == radiopreset_LongSlow) {
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW;
} else if (selected == radiopreset_LongModerate) {
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE;
} else if (selected == radiopreset_LongFast) {
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
} else if (selected == radiopreset_MediumSlow) {
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW;
} else if (selected == radiopreset_MediumFast) {
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST;
} else if (selected == radiopreset_ShortSlow) {
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW;
} else if (selected == radiopreset_ShortFast) {
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST;
} else if (selected == radiopreset_ShortTurbo) {
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO;
}
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::TwelveHourPicker()
{
static const char *optionsArray[] = {"Back", "12-hour", "24-hour"};
@@ -370,11 +317,7 @@ void menuHandler::TZPicker()
void menuHandler::clockMenu()
{
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[] = {"Back", "Time Format", "Timezone"};
#else
static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"};
#endif
enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Clock Action";
@@ -398,11 +341,8 @@ void menuHandler::clockMenu()
void menuHandler::messageResponseMenu()
{
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply Preset"};
#else
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
#endif
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
int options = 3;
@@ -416,11 +356,7 @@ void menuHandler::messageResponseMenu()
optionsEnumArray[options++] = Aloud;
#endif
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Message";
#else
bannerOptions.message = "Message Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -473,11 +409,7 @@ void menuHandler::homeBaseMenu()
optionsArray[options] = "Send Node Info";
}
optionsEnumArray[options++] = Position;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "New Preset";
#else
optionsArray[options] = "New Preset Msg";
#endif
optionsEnumArray[options++] = Preset;
if (kb_found) {
optionsArray[options] = "New Freetext Msg";
@@ -485,11 +417,7 @@ void menuHandler::homeBaseMenu()
}
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Home";
#else
bannerOptions.message = "Home Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -528,11 +456,6 @@ void menuHandler::homeBaseMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::textMessageMenu()
{
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
}
void menuHandler::textMessageBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, enumEnd };
@@ -579,17 +502,11 @@ void menuHandler::systemBaseMenu()
optionsArray[options] = "Frame Visiblity Toggle";
optionsEnumArray[options++] = FrameToggles;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Bluetooth";
#else
optionsArray[options] = "Bluetooth Toggle";
#endif
optionsEnumArray[options++] = Bluetooth;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Power";
#else
optionsArray[options] = "Reboot/Shutdown";
#endif
optionsEnumArray[options++] = PowerMenu;
if (test_enabled) {
@@ -598,11 +515,7 @@ void menuHandler::systemBaseMenu()
}
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "System";
#else
bannerOptions.message = "System Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -638,11 +551,7 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd };
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[enumEnd] = {"Back", "New Preset"};
#else
static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
#endif
static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2;
@@ -650,19 +559,13 @@ void menuHandler::favoriteBaseMenu()
optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext;
}
#if !defined(M5STACK_UNITC6L)
optionsArray[options] = "Trace Route";
optionsEnumArray[options++] = TraceRoute;
#endif
optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Favorites";
#else
bannerOptions.message = "Favorites Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -685,11 +588,11 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu()
{
enum optionsNumbers { Back, GPSToggle, GPSFormat, CompassMenu, CompassCalibrate, enumEnd };
enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd };
static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "GPS Format", "Compass"};
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, GPSFormat, CompassMenu};
int options = 4;
static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"};
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
int options = 3;
if (accelerometerThread) {
optionsArray[options] = "Compass Calibrate";
@@ -705,9 +608,6 @@ void menuHandler::positionBaseMenu()
if (selected == GPSToggle) {
menuQueue = gps_toggle_menu;
screen->runNow();
} else if (selected == GPSFormat) {
menuQueue = gps_format_menu;
screen->runNow();
} else if (selected == CompassMenu) {
menuQueue = compass_point_north_menu;
screen->runNow();
@@ -721,19 +621,11 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu()
{
enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd };
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset Node"};
#else
static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"};
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray;
#if defined(M5STACK_UNITC6L)
bannerOptions.optionsCount = 3;
#else
bannerOptions.optionsCount = 5;
#endif
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Favorite) {
menuQueue = add_favorite;
@@ -834,62 +726,13 @@ void menuHandler::GPSToggleMenu()
bannerOptions.InitialSelected = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED ? 1 : 2;
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::GPSFormatMenu()
{
static const char *optionsArray[] = {"Back",
isHighResolution ? "Decimal Degrees" : "DEC",
isHighResolution ? "Degrees Minutes Seconds" : "DMS",
isHighResolution ? "Universal Transverse Mercator" : "UTM",
isHighResolution ? "Military Grid Reference System" : "MGRS",
isHighResolution ? "Open Location Code" : "OLC",
isHighResolution ? "Ordnance Survey Grid Ref" : "OSGR",
isHighResolution ? "Maidenhead Locator" : "MLS"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "GPS Format";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 8;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 2) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 3) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 4) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 5) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 6) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 7) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS;
service->reloadConfig(SEGMENT_CONFIG);
} else {
menuQueue = position_base_menu;
screen->runNow();
}
};
bannerOptions.InitialSelected = uiconfig.gps_format + 1;
screen->showOverlayBanner(bannerOptions);
}
#endif
void menuHandler::BluetoothToggleMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Bluetooth";
#else
bannerOptions.message = "Toggle Bluetooth";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -1081,11 +924,7 @@ void menuHandler::rebootMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Reboot";
#else
bannerOptions.message = "Reboot Device?";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -1105,11 +944,7 @@ void menuHandler::shutdownMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Shutdown";
#else
bannerOptions.message = "Shutdown Device?";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -1126,12 +961,7 @@ void menuHandler::shutdownMenu()
void menuHandler::addFavoriteMenu()
{
#if defined(M5STACK_UNITC6L)
screen->showNodePicker("Node Favorite", 30000, [](uint32_t nodenum) -> void {
#else
screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
#endif
LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
@@ -1344,11 +1174,7 @@ void menuHandler::powerMenu()
#endif
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Power";
#else
bannerOptions.message = "Reboot / Shutdown";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -1527,9 +1353,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case device_role_picker:
DeviceRolePicker();
break;
case radio_preset_picker:
RadioPresetPicker();
break;
case no_timeout_lora_picker:
LoraRegionPicker(0);
break;
@@ -1555,9 +1378,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case gps_toggle_menu:
GPSToggleMenu();
break;
case gps_format_menu:
GPSFormatMenu();
break;
#endif
case compass_point_north_menu:
compassNorthMenu();

View File

@@ -12,7 +12,6 @@ class menuHandler
lora_Menu,
lora_picker,
device_role_picker,
radio_preset_picker,
no_timeout_lora_picker,
TZ_picker,
twelve_hour_picker,
@@ -20,7 +19,6 @@ class menuHandler
clock_menu,
position_base_menu,
gps_toggle_menu,
gps_format_menu,
compass_point_north_menu,
reset_node_db_menu,
buzzermodemenupicker,
@@ -51,7 +49,6 @@ class menuHandler
static void LoraRegionPicker(uint32_t duration = 30000);
static void loraMenu();
static void DeviceRolePicker();
static void RadioPresetPicker();
static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
static void clockMenu();
@@ -66,7 +63,6 @@ class menuHandler
static void positionBaseMenu();
static void compassNorthMenu();
static void GPSToggleMenu();
static void GPSFormatMenu();
static void BuzzerModeMenu();
static void switchToMUIMenu();
static void TFTColorPickerMenu(OLEDDisplay *display);
@@ -86,7 +82,6 @@ class menuHandler
static void screenOptionsMenu();
static void powerMenu();
static void FrameToggles_menu();
static void textMessageMenu();
private:
static void saveUIConfig();

View File

@@ -181,19 +181,12 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
#if defined(M5STACK_UNITC6L)
const int fixedTopHeight = 24;
const int windowX = 0;
const int windowY = fixedTopHeight;
const int windowWidth = 64;
const int windowHeight = SCREEN_HEIGHT - fixedTopHeight;
#else
const int navHeight = FONT_HEIGHT_SMALL;
const int scrollBottom = SCREEN_HEIGHT - navHeight;
const int usableHeight = scrollBottom;
const int textWidth = SCREEN_WIDTH;
#endif
bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
bool isBold = config.display.heading_bold;
@@ -208,11 +201,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
graphics::drawCommonHeader(display, x, y, titleStr);
const char *messageString = "No messages";
int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2);
#if defined(M5STACK_UNITC6L)
display->drawString(center_text, windowY + (windowHeight / 2) - (FONT_HEIGHT_SMALL / 2) - 5, messageString);
#else
display->drawString(center_text, getTextPositions(display)[2], messageString);
#endif
return;
}
@@ -220,10 +209,6 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
char headerStr[80];
const char *sender = "???";
#if defined(M5STACK_UNITC6L)
if (node && node->has_user)
sender = node->user.short_name;
#else
if (node && node->has_user) {
if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) {
sender = node->user.long_name;
@@ -231,7 +216,6 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
sender = node->user.short_name;
}
}
#endif
uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
uint8_t timestampHours, timestampMinutes;
int32_t daysAgo;
@@ -251,61 +235,10 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
sender);
}
} else {
#if defined(M5STACK_UNITC6L)
snprintf(headerStr, sizeof(headerStr), "%s from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
sender);
#else
snprintf(headerStr, sizeof(headerStr), "%s ago from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
sender);
#endif
}
#if defined(M5STACK_UNITC6L)
graphics::drawCommonHeader(display, x, y, titleStr);
int headerY = getTextPositions(display)[1];
display->drawString(x, headerY, headerStr);
for (int separatorX = 0; separatorX < SCREEN_WIDTH; separatorX += 2) {
display->setPixel(separatorX, fixedTopHeight - 1);
}
cachedLines.clear();
std::string fullMsg(messageBuf);
std::string currentLine;
for (size_t i = 0; i < fullMsg.size();) {
unsigned char c = fullMsg[i];
size_t charLen = 1;
if ((c & 0xE0) == 0xC0)
charLen = 2;
else if ((c & 0xF0) == 0xE0)
charLen = 3;
else if ((c & 0xF8) == 0xF0)
charLen = 4;
std::string nextChar = fullMsg.substr(i, charLen);
std::string testLine = currentLine + nextChar;
if (display->getStringWidth(testLine.c_str()) > windowWidth) {
cachedLines.push_back(currentLine);
currentLine = nextChar;
} else {
currentLine = testLine;
}
i += charLen;
}
if (!currentLine.empty())
cachedLines.push_back(currentLine);
cachedHeights = calculateLineHeights(cachedLines, emotes);
int yOffset = windowY;
int linesDrawn = 0;
for (size_t i = 0; i < cachedLines.size(); ++i) {
if (linesDrawn >= 2)
break;
int lineHeight = cachedHeights[i];
if (yOffset + lineHeight > windowY + windowHeight)
break;
drawStringWithEmotes(display, windowX, yOffset, cachedLines[i], emotes, numEmotes);
yOffset += lineHeight;
linesDrawn++;
}
screen->forceDisplay();
#else
uint32_t now = millis();
#ifndef EXCLUDE_EMOJI
// === Bounce animation setup ===
@@ -422,7 +355,6 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Draw header at the end to sort out overlapping elements
graphics::drawCommonHeader(display, x, y, titleStr);
#endif
}
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)

View File

@@ -21,10 +21,6 @@ extern bool haveGlyphs(const char *str);
// Global screen instance
extern graphics::Screen *screen;
#if defined(M5STACK_UNITC6L)
static uint32_t lastSwitchTime = 0;
#else
#endif
namespace graphics
{
namespace NodeListRenderer
@@ -397,11 +393,9 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
{
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
#if defined(M5STACK_UNITC6L)
int columnWidth = display->getWidth();
#else
int columnWidth = display->getWidth() / 2;
#endif
display->clear();
// Draw the battery/time header
@@ -414,11 +408,8 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
int visibleNodeRows = totalRowsAvailable;
#if defined(M5STACK_UNITC6L)
int totalColumns = 1;
#else
int totalColumns = 2;
#endif
int startIndex = scrollIndex * visibleNodeRows * totalColumns;
if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) {
startIndex++; // skip own node
@@ -454,14 +445,12 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
}
}
#if !defined(M5STACK_UNITC6L)
// Draw column separator
if (shownCount > 0) {
const int firstNodeY = y + 3;
drawColumnSeparator(display, x, firstNodeY, lastNodeY);
}
#endif
const int scrollStartY = y + 3;
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
}
@@ -479,13 +468,6 @@ void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state,
unsigned long now = millis();
#if defined(M5STACK_UNITC6L)
display->clear();
if (now - lastSwitchTime >= 3000) {
display->display();
lastSwitchTime = now;
}
#endif
// On very first call (on boot or state enter)
if (lastRenderedMode == MODE_COUNT) {
currentMode = MODE_LAST_HEARD;
@@ -540,14 +522,6 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
double lat = DegD(ourNode->position.latitude_i);
double lon = DegD(ourNode->position.longitude_i);
#if defined(M5STACK_UNITC6L)
display->clear();
uint32_t now = millis();
if (now - lastSwitchTime >= 2000) {
display->display();
lastSwitchTime = now;
}
#endif
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
#if HAS_GPS
if (screen->hasHeading()) {

View File

@@ -49,6 +49,7 @@ const int *NotificationRenderer::optionsEnumPtr = nullptr;
std::function<void(int)> NotificationRenderer::alertBannerCallback = NULL;
bool NotificationRenderer::pauseBanner = false;
notificationTypeEnum NotificationRenderer::current_notification_type = notificationTypeEnum::none;
uint32_t NotificationRenderer::numDigits = 0;
uint32_t NotificationRenderer::currentNumber = 0;
VirtualKeyboard *NotificationRenderer::virtualKeyboard = nullptr;
@@ -85,9 +86,13 @@ void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiStat
void NotificationRenderer::resetBanner()
{
notificationTypeEnum previousType = current_notification_type;
alertBannerMessage[0] = '\0';
current_notification_type = notificationTypeEnum::none;
OnScreenKeyboardModule::instance().clearPopup();
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent.kbchar = 0;
curSelected = 0;
@@ -100,6 +105,13 @@ void NotificationRenderer::resetBanner()
currentNumber = 0;
nodeDB->pause_sort(false);
// If we're exiting from text_input (virtual keyboard), stop module and trigger frame update
// to ensure any messages received during keyboard use are now displayed
if (previousType == notificationTypeEnum::text_input && screen) {
OnScreenKeyboardModule::instance().stop(false);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
}
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
@@ -250,11 +262,9 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
}
// Handle input
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
alertBannerCallback(selectedNodenum);
@@ -367,11 +377,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
// Handle input
if (alertBannerOptions > 0) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
if (optionsEnumPtr != nullptr) {
@@ -495,135 +503,6 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
// count lines
uint16_t boxWidth = hPadding * 2 + maxWidth;
#if defined(M5STACK_UNITC6L)
if (needs_bell) {
if (isHighResolution && boxWidth <= 150)
boxWidth += 26;
if (!isHighResolution && boxWidth <= 100)
boxWidth += 20;
}
uint16_t screenHeight = display->height();
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
uint16_t boxHeight = contentHeight + vPadding * 2;
if (visibleTotalLines == 1)
boxHeight += (isHighResolution ? 4 : 3);
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
if (totalLines > visibleTotalLines)
boxWidth += (isHighResolution ? 4 : 2);
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
if (visibleTotalLines == 1) {
boxTop += 25;
}
if (alertBannerOptions < 3) {
int missingLines = 3 - alertBannerOptions;
int moveUp = missingLines * (effectiveLineHeight / 2);
boxTop -= moveUp;
if (boxTop < 0)
boxTop = 0;
}
// === Draw Box ===
display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, boxWidth, boxHeight);
display->setColor(WHITE);
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
display->fillRect(boxLeft - 2, boxTop, 1, boxHeight);
display->fillRect(boxLeft + boxWidth + 1, boxTop, 1, boxHeight);
display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1);
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
display->setColor(WHITE);
int16_t lineY = boxTop + vPadding;
int swingRange = 8;
static int swingOffset = 0;
static bool swingRight = true;
static unsigned long lastSwingTime = 0;
unsigned long now = millis();
int swingSpeedMs = 10 / (swingRange * 2);
if (now - lastSwingTime >= (unsigned long)swingSpeedMs) {
lastSwingTime = now;
if (swingRight) {
swingOffset++;
if (swingOffset >= swingRange)
swingRight = false;
} else {
swingOffset--;
if (swingOffset <= 0)
swingRight = true;
}
}
for (int i = 0; i < lineCount; i++) {
bool isTitle = (i == 0);
int globalOptionIndex = (i - 1) + firstOptionToShow;
bool isSelectedOption = (!isTitle && globalOptionIndex >= 0 && globalOptionIndex == curSelected);
uint16_t visibleWidth = 64 - hPadding * 2;
if (totalLines > visibleTotalLines)
visibleWidth -= 6;
char lineBuffer[lineLengths[i] + 1];
strncpy(lineBuffer, lines[i], lineLengths[i]);
lineBuffer[lineLengths[i]] = '\0';
if (isTitle) {
if (visibleTotalLines == 1) {
display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
display->setColor(WHITE);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
} else {
display->setColor(WHITE);
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
display->setColor(BLACK);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
display->setColor(WHITE);
if (needs_bell) {
int bellY = boxTop + (FONT_HEIGHT_SMALL - 8) / 2;
display->drawXbm(boxLeft + (boxWidth - lineWidths[i]) / 2 - 10, bellY, 8, 8, bell_alert);
display->drawXbm(boxLeft + (boxWidth + lineWidths[i]) / 2 + 2, bellY, 8, 8, bell_alert);
}
}
lineY = boxTop + effectiveLineHeight + 1;
} else if (isSelectedOption) {
display->setColor(WHITE);
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
display->setColor(BLACK);
if (lineLengths[i] > 15 && lineWidths[i] > visibleWidth) {
int textX = boxLeft + hPadding + swingOffset;
display->drawString(textX, lineY - 1, lineBuffer);
} else {
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY - 1, lineBuffer);
}
display->setColor(WHITE);
lineY += effectiveLineHeight;
} else {
display->setColor(BLACK);
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
display->setColor(WHITE);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY, lineBuffer);
lineY += effectiveLineHeight;
}
}
if (totalLines > visibleTotalLines) {
const uint8_t scrollBarWidth = 5;
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
float ratio = (float)visibleTotalLines / totalLines;
uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4);
float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines);
uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight);
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
}
#else
if (needs_bell) {
if (isHighResolution && boxWidth <= 150)
boxWidth += 26;
@@ -712,7 +591,6 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
}
#endif
}
/// Draw the last text message we received
@@ -743,94 +621,26 @@ void NotificationRenderer::drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUi
void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state)
{
if (virtualKeyboard) {
// Check for timeout and auto-exit if needed
if (virtualKeyboard->isTimedOut()) {
LOG_INFO("Virtual keyboard timeout - auto-exiting");
// Cancel virtual keyboard - call callback with empty string to indicate timeout
auto callback = textInputCallback; // Store callback before clearing
// Delegate session to OnScreenKeyboardModule
auto &osk = OnScreenKeyboardModule::instance();
// Clean up first to prevent re-entry
delete virtualKeyboard;
virtualKeyboard = nullptr;
textInputCallback = nullptr;
resetBanner();
// Call callback after cleanup
if (callback) {
callback("");
}
// Restore normal overlays
if (screen) {
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
return;
}
if (inEvent.inputEvent != INPUT_BROKER_NONE) {
if (inEvent.inputEvent == INPUT_BROKER_UP) {
// high frequency for move cursor left/right than up/down with encoders
extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorLeft();
} else {
virtualKeyboard->moveCursorUp();
}
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN) {
extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorRight();
} else {
virtualKeyboard->moveCursorDown();
}
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) {
virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
virtualKeyboard->moveCursorUp();
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
virtualKeyboard->moveCursorDown();
} else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
virtualKeyboard->handlePress();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) {
virtualKeyboard->handleLongPress();
} else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) {
auto callback = textInputCallback;
delete virtualKeyboard;
virtualKeyboard = nullptr;
textInputCallback = nullptr;
resetBanner();
if (callback) {
callback("");
}
if (screen) {
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
return;
}
// Consume the event after processing for virtual keyboard
inEvent.inputEvent = INPUT_BROKER_NONE;
}
// Clear the screen to avoid overlapping with underlying frames or overlays
display->setColor(BLACK);
display->fillRect(0, 0, display->getWidth(), display->getHeight());
display->setColor(WHITE);
// Draw the virtual keyboard
virtualKeyboard->draw(display, 0, 0);
} else {
// If virtualKeyboard is null, reset the banner to avoid getting stuck
LOG_INFO("Virtual keyboard is null - resetting banner");
if (!osk.isActive()) {
LOG_INFO("Virtual keyboard is not active - resetting banner");
resetBanner();
return;
}
if (inEvent.inputEvent != INPUT_BROKER_NONE) {
osk.handleInput(inEvent);
inEvent.inputEvent = INPUT_BROKER_NONE; // consume
}
// Draw. If draw returns false, session ended (timeout or cancel)
if (!osk.draw(display)) {
// Session ended, ensure banner reset and restore frames
resetBanner();
if (screen)
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
}
@@ -839,5 +649,152 @@ bool NotificationRenderer::isOverlayBannerShowing()
return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil);
}
void NotificationRenderer::showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs)
{
if (!title || !content || current_notification_type != notificationTypeEnum::text_input)
return;
OnScreenKeyboardModule::instance().showPopup(title, content, durationMs);
}
// drawKeyboardMessagePopup removed; OnScreenKeyboardModule handles popup drawing within draw()
// Custom inverted color version for keyboard popup - black background with white text
void NotificationRenderer::drawInvertedNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[],
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth)
{
bool is_picker = false;
uint16_t lineCount = 0;
// === Layout Configuration ===
constexpr uint16_t hPadding = 5;
constexpr uint16_t vPadding = 2;
bool needs_bell = false;
uint16_t lineWidths[totalLines] = {0};
uint16_t lineLengths[totalLines] = {0};
if (maxWidth != 0)
is_picker = true;
// Setup font and alignment
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
while (lines[lineCount] != nullptr) {
auto newlinePointer = strchr(lines[lineCount], '\n');
if (newlinePointer)
lineLengths[lineCount] = (newlinePointer - lines[lineCount]);
else
lineLengths[lineCount] = strlen(lines[lineCount]);
lineWidths[lineCount] = display->getStringWidth(lines[lineCount], lineLengths[lineCount], true);
if (!is_picker) {
if (lineWidths[lineCount] > maxWidth)
maxWidth = lineWidths[lineCount];
}
lineCount++;
}
uint16_t boxWidth = hPadding * 2 + maxWidth;
if (needs_bell) {
if (isHighResolution && boxWidth <= 150)
boxWidth += 26;
if (!isHighResolution && boxWidth <= 100)
boxWidth += 20;
}
uint16_t screenHeight = display->height();
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
uint16_t boxHeight = contentHeight + vPadding * 2;
if (visibleTotalLines == 1) {
boxHeight += (isHighResolution) ? 4 : 3;
}
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
if (totalLines > visibleTotalLines) {
boxWidth += (isHighResolution) ? 4 : 2;
}
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
// === Draw Box with INVERTED COLORS ===
// Add outer separation pixels (1-pixel white background around the box)
display->setColor(WHITE);
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2);
// Make outer corners round by filling back with背景色 (BLACK for separation)
display->setColor(BLACK);
// Top-left outer corner
display->fillRect(boxLeft - 1, boxTop - 1, 1, 1);
// Top-right outer corner
display->fillRect(boxLeft + boxWidth, boxTop - 1, 1, 1);
// Bottom-left outer corner
display->fillRect(boxLeft - 1, boxTop + boxHeight, 1, 1);
// Bottom-right outer corner
display->fillRect(boxLeft + boxWidth, boxTop + boxHeight, 1, 1);
// Draw single pixel black border
display->setColor(BLACK);
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
// Make inner corners round by filling white pixels at corners
display->setColor(WHITE);
display->fillRect(boxLeft, boxTop, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1);
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
// Fill interior with BLACK (inverted)
display->setColor(BLACK);
display->fillRect(boxLeft + 1, boxTop + 1, boxWidth - 2, boxHeight - 2);
// === Draw Content with WHITE text on BLACK background ===
display->setColor(WHITE);
int16_t lineY = boxTop + vPadding;
for (int i = 0; i < lineCount; i++) {
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
char lineBuffer[lineLengths[i] + 1];
strncpy(lineBuffer, lines[i], lineLengths[i]);
lineBuffer[lineLengths[i]] = '\0';
// For keyboard popup, treat first line as title if it's different from others
if (i == 0 && lineCount > 1) {
// Title line - use inverted colors (white background, black text)
display->setColor(WHITE);
int background_yOffset = 1;
// Check for low hanging characters
if (strchr(lineBuffer, 'p') || strchr(lineBuffer, 'g') || strchr(lineBuffer, 'y') || strchr(lineBuffer, 'j')) {
background_yOffset = -1;
}
display->fillRect(boxLeft + 1, boxTop + 1, boxWidth - 2, effectiveLineHeight - background_yOffset);
display->setColor(BLACK);
int yOffset = 3;
display->drawString(textX, lineY - yOffset, lineBuffer);
display->setColor(WHITE); // Reset to white for next lines
lineY += (effectiveLineHeight - 2 - background_yOffset);
} else {
// Content lines - white text on black background
display->drawString(textX, lineY, lineBuffer);
lineY += effectiveLineHeight;
}
}
// === Scroll Bar (if needed) ===
if (totalLines > visibleTotalLines) {
const uint8_t scrollBarWidth = 5;
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
float ratio = (float)visibleTotalLines / totalLines;
uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4);
float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines);
uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight);
display->setColor(WHITE);
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
}
}
} // namespace graphics
#endif

View File

@@ -4,6 +4,7 @@
#include "OLEDDisplayUi.h"
#include "graphics/Screen.h"
#include "graphics/VirtualKeyboard.h"
#include "modules/OnScreenKeyboardModule.h"
#include <functional>
#include <string>
#define MAX_LINES 5
@@ -31,6 +32,7 @@ class NotificationRenderer
static bool pauseBanner;
static void resetBanner();
static void showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs);
static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state);
@@ -38,6 +40,8 @@ class NotificationRenderer
static void drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1],
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);
static void drawInvertedNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[],
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);
static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);

View File

@@ -20,9 +20,7 @@
// External variables
extern graphics::Screen *screen;
#if defined(M5STACK_UNITC6L)
static uint32_t lastSwitchTime = 0;
#endif
namespace graphics
{
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
@@ -118,122 +116,64 @@ void UIRenderer::drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, con
}
// Draw GPS status coordinates
void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps,
const char *mode)
void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps)
{
auto gpsFormat = uiconfig.gps_format;
auto gpsFormat = config.display.gps_format;
char displayLine[32];
if (!gps->getIsConnected() && !config.position.fixed_position) {
strcpy(displayLine, "No GPS present");
display->drawString(x, y, displayLine);
display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else if (!gps->getHasLock() && !config.position.fixed_position) {
if (strcmp(mode, "line1") == 0) {
strcpy(displayLine, "No GPS Lock");
display->drawString(x, y, displayLine);
}
strcpy(displayLine, "No GPS Lock");
display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else {
geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
if (gpsFormat != meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS) {
char coordinateLine_1[22];
char coordinateLine_2[22];
if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees
snprintf(coordinateLine_1, sizeof(coordinateLine_1), "Lat: %f", geoCoord.getLatitude() * 1e-7);
snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %f", geoCoord.getLongitude() * 1e-7);
} else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator
snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %06u E", geoCoord.getUTMZone(),
geoCoord.getUTMBand(), geoCoord.getUTMEasting());
snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%07u N", geoCoord.getUTMNorthing());
} else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System
snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %1c%1c", geoCoord.getMGRSZone(),
geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k());
snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getMGRSEasting(),
geoCoord.getMGRSNorthing());
} else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC) { // Open Location Code
geoCoord.getOLCCode(coordinateLine_1);
coordinateLine_2[0] = '\0';
} else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference
if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') { // OSGR is only valid around the UK region
snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%s", "Out of Boundary");
coordinateLine_2[0] = '\0';
if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) {
char coordinateLine[22];
if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees
snprintf(coordinateLine, sizeof(coordinateLine), "%f %f", geoCoord.getLatitude() * 1e-7,
geoCoord.getLongitude() * 1e-7);
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator
snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %06u %07u", geoCoord.getUTMZone(), geoCoord.getUTMBand(),
geoCoord.getUTMEasting(), geoCoord.getUTMNorthing());
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System
snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %1c%1c %05u %05u", geoCoord.getMGRSZone(),
geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k(),
geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing());
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) { // Open Location Code
geoCoord.getOLCCode(coordinateLine);
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference
if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region
snprintf(coordinateLine, sizeof(coordinateLine), "%s", "Out of Boundary");
else
snprintf(coordinateLine, sizeof(coordinateLine), "%1c%1c %05u %05u", geoCoord.getOSGRE100k(),
geoCoord.getOSGRN100k(), geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing());
}
// If fixed position, display text "Fixed GPS" alternating with the coordinates.
if (config.position.fixed_position) {
if ((millis() / 10000) % 2) {
display->drawString(x + (display->getWidth() - (display->getStringWidth(coordinateLine))) / 2, y,
coordinateLine);
} else {
snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%1c%1c", geoCoord.getOSGRE100k(),
geoCoord.getOSGRN100k());
snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getOSGREasting(),
geoCoord.getOSGRNorthing());
display->drawString(x + (display->getWidth() - (display->getStringWidth("Fixed GPS"))) / 2, y, "Fixed GPS");
}
} else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS) { // Maidenhead Locator System
double lat = geoCoord.getLatitude() * 1e-7;
double lon = geoCoord.getLongitude() * 1e-7;
// Normalize
if (lat > 90.0)
lat = 90.0;
if (lat < -90.0)
lat = -90.0;
while (lon < -180.0)
lon += 360.0;
while (lon >= 180.0)
lon -= 360.0;
double adjLon = lon + 180.0;
double adjLat = lat + 90.0;
char maiden[10]; // enough for 8-char + null
// Field (2 letters)
int lonField = int(adjLon / 20.0);
int latField = int(adjLat / 10.0);
adjLon -= lonField * 20.0;
adjLat -= latField * 10.0;
// Square (2 digits)
int lonSquare = int(adjLon / 2.0);
int latSquare = int(adjLat / 1.0);
adjLon -= lonSquare * 2.0;
adjLat -= latSquare * 1.0;
// Subsquare (2 letters)
double lonUnit = 2.0 / 24.0;
double latUnit = 1.0 / 24.0;
int lonSub = int(adjLon / lonUnit);
int latSub = int(adjLat / latUnit);
snprintf(maiden, sizeof(maiden), "%c%c%c%c%c%c", 'A' + lonField, 'A' + latField, '0' + lonSquare, '0' + latSquare,
'A' + lonSub, 'A' + latSub);
snprintf(coordinateLine_1, sizeof(coordinateLine_1), "MH: %s", maiden);
coordinateLine_2[0] = '\0'; // only need one line
} else {
display->drawString(x + (display->getWidth() - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
}
if (strcmp(mode, "line1") == 0) {
display->drawString(x, y, coordinateLine_1);
} else if (strcmp(mode, "line2") == 0) {
display->drawString(x, y, coordinateLine_2);
} else if (strcmp(mode, "combined") == 0) {
display->drawString(x, y, coordinateLine_1);
if (coordinateLine_2[0] != '\0') {
display->drawString(x + display->getStringWidth(coordinateLine_1), y, coordinateLine_2);
}
}
} else {
char coordinateLine_1[22];
char coordinateLine_2[22];
snprintf(coordinateLine_1, sizeof(coordinateLine_1), "Lat: %2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(),
geoCoord.getDMSLatMin(), geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP());
snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(),
geoCoord.getDMSLonMin(), geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP());
if (strcmp(mode, "line1") == 0) {
display->drawString(x, y, coordinateLine_1);
} else if (strcmp(mode, "line2") == 0) {
display->drawString(x, y, coordinateLine_2);
} else { // both
display->drawString(x, y, coordinateLine_1);
display->drawString(x, y + 10, coordinateLine_2);
}
char latLine[22];
char lonLine[22];
snprintf(latLine, sizeof(latLine), "%2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(),
geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP());
snprintf(lonLine, sizeof(lonLine), "%3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(),
geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP());
display->drawString(x + (display->getWidth() - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1,
latLine);
display->drawString(x + (display->getWidth() - (display->getStringWidth(lonLine))) / 2, y, lonLine);
}
}
}
@@ -278,6 +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)
{
if (favoritedNodes.empty())
return;
@@ -289,15 +230,8 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
return;
display->clear();
#if defined(M5STACK_UNITC6L)
uint32_t now = millis();
if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
{
display->display();
lastSwitchTime = now;
}
#endif
currentFavoriteNodeNum = node->num;
// === Create the shortName and title string ===
const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node";
@@ -316,13 +250,9 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
// List of available macro Y positions in order, from top to bottom.
int line = 1; // which slot to use next
std::string usernameStr;
// === 1. Long Name (always try to show first) ===
#if defined(M5STACK_UNITC6L)
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr;
#else
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
#endif
// === 1. Long Name (always try to show first) ===
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
if (username) {
usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case
// Print node's long name (e.g. "Backpack Node")
@@ -377,7 +307,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
if (seenStr[0] && line < 5) {
display->drawString(x, getTextPositions(display)[line++], seenStr);
}
#if !defined(M5STACK_UNITC6L)
// === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = "";
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
@@ -549,7 +479,6 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
}
// else show nothing
}
#endif
}
// ****************************
@@ -563,11 +492,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
int line = 1;
// === Header ===
#if defined(M5STACK_UNITC6L)
graphics::drawCommonHeader(display, x, y, "Home");
#else
graphics::drawCommonHeader(display, x, y, "");
#endif
// === Content below header ===
@@ -582,25 +507,20 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
config.display.heading_bold = false;
// Display Region and Channel Utilization
#if defined(M5STACK_UNITC6L)
drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
#else
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
#endif
char uptimeStr[32] = "";
uint32_t uptime = millis() / 1000;
uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
#if !defined(M5STACK_UNITC6L)
if (days)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
#endif
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
// === Second Row: Satellites and Voltage ===
@@ -629,21 +549,6 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
}
#endif
#if defined(M5STACK_UNITC6L)
line += 1;
// === Node Identity ===
int textWidth = 0;
int nameX = 0;
char shortnameble[35];
snprintf(shortnameble, sizeof(shortnameble), "%s",
graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : "");
// === ShortName Centered ===
textWidth = display->getStringWidth(shortnameble);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
#else
if (powerStatus->getHasBattery()) {
char batStr[20];
int batV = powerStatus->getBatteryVoltageMv() / 1000;
@@ -736,6 +641,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
int textWidth = 0;
int nameX = 0;
int yOffset = (isHighResolution) ? 0 : 5;
const char *longName = nullptr;
std::string longNameStr;
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
@@ -768,7 +674,6 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
}
#endif
}
// Start Functions to write date/time to the screen
@@ -927,28 +832,6 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
// needs to be drawn relative to x and y
// draw centered icon left to right and centered above the one line of app text
#if defined(M5STACK_UNITC6L)
display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
// Draw region in upper left
if (upperMsg) {
int msgWidth = display->getStringWidth(upperMsg);
int msgX = x + (SCREEN_WIDTH - msgWidth) / 2;
int msgY = y;
display->drawString(msgX, msgY, upperMsg);
}
// Draw version and short name in bottom middle
char buf[25];
snprintf(buf, sizeof(buf), "%s %s", xstr(APP_VERSION_SHORT),
graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : "");
display->drawString(x + getStringCenteredX(buf), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, buf);
screen->forceDisplay();
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
#else
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
icon_width, icon_height, icon_bits);
@@ -957,6 +840,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
const char *title = "meshtastic.org";
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL);
// Draw region in upper left
if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg);
@@ -971,7 +855,6 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
screen->forceDisplay();
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
#endif
}
// ****************************
@@ -1071,11 +954,11 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
#if defined(USE_EINK)
// E-Ink: skip seconds, show only days/hours/mins
if (days > 0) {
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
snprintf(buf, sizeof(buf), " Last: %ud %uh", days, hours);
} else if (hours > 0) {
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
snprintf(buf, sizeof(buf), " Last: %uh %um", hours, mins);
} else {
snprintf(buf, sizeof(buf), "Last: %um", mins);
snprintf(buf, sizeof(buf), " Last: %um", mins);
}
#else
// Non E-Ink: include seconds where useful
@@ -1095,16 +978,29 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
display->drawString(0, getTextPositions(display)[line++], "Last: ?");
}
// === Third Row: Line 1 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line1");
// === Third Row: Latitude ===
char latStr[32];
snprintf(latStr, sizeof(latStr), "Lat: %.5f", geoCoord.getLatitude() * 1e-7);
display->drawString(x, getTextPositions(display)[line++], latStr);
if (uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC &&
uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS) {
// === Fourth Row: Line 2 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2");
// === Fourth Row: Longitude ===
char lonStr[32];
snprintf(lonStr, sizeof(lonStr), "Lon: %.5f", geoCoord.getLongitude() * 1e-7);
display->drawString(x, getTextPositions(display)[line++], lonStr);
// === Fifth Row: Altitude ===
char DisplayLineTwo[32] = {0};
int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
? ourNode->position.altitude
: geoCoord.getAltitude();
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET);
} else {
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0im", geoCoord.getAltitude());
}
display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
}
#if !defined(M5STACK_UNITC6L)
// === Draw Compass if heading is valid ===
if (validHeading) {
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
@@ -1187,7 +1083,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
}
}
#endif
#endif // HAS_GPS
}
#ifdef USERPREFS_OEM_TEXT
@@ -1280,13 +1175,14 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
const int totalWidth = (pageEnd - pageStart) * iconSize + (pageEnd - pageStart - 1) * spacing;
const int xStart = (SCREEN_WIDTH - totalWidth) / 2;
// Only show bar briefly after switching frames
static uint32_t navBarLastShown = 0;
static bool cosmeticRefreshDone = false;
bool navBarVisible = millis() - lastFrameChangeTime <= ICON_DISPLAY_DURATION_MS;
int y = navBarVisible ? (SCREEN_HEIGHT - iconSize - 1) : SCREEN_HEIGHT;
#if defined(USE_EINK)
// Only show bar briefly after switching frames
static uint32_t navBarLastShown = 0;
static bool cosmeticRefreshDone = false;
static bool navBarPrevVisible = false;
if (navBarVisible && !navBarPrevVisible) {
@@ -1343,6 +1239,7 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setColor(WHITE);
}
}
// Knock the corners off the square
display->setColor(BLACK);
display->drawRect(rectX, y - 2, 1, 1);

View File

@@ -38,8 +38,7 @@ class UIRenderer
// GPS status functions
static void drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
static void drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus,
const char *mode = "line1");
static void drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
static void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
static void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);

View File

@@ -290,7 +290,7 @@ const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100,
#define chirpy_width 38
#define chirpy_height 50
const uint8_t chirpy[] = {
static unsigned char chirpy[] = {
0xfe, 0xff, 0xff, 0xff, 0xdf, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01,
0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01, 0x00,
0x00, 0x00, 0xe0, 0x81, 0xff, 0xff, 0x7f, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xcf, 0x7f,
@@ -306,7 +306,7 @@ const uint8_t chirpy[] = {
#define chirpy_width_hirez 76
#define chirpy_height_hirez 100
const uint8_t chirpy_hirez[] = {
static unsigned char chirpy_hirez[] = {
0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
@@ -358,11 +358,7 @@ const uint8_t chirpy_hirez[] = {
#define chirpy_small_image_width 8
#define chirpy_small_image_height 8
const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
#ifdef M5STACK_UNITC6L
#include "img/icon_small.xbm"
#else
#include "img/icon.xbm"
#endif
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");

View File

@@ -1,30 +0,0 @@
#ifndef USERPREFS_HAS_SPLASH
#define icon_width 50
#define icon_height 20
static uint8_t icon_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x80, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x80,
0x07, 0xc0, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x0f,
0xc0, 0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xe0,
0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x1f,
0x00, 0x00, 0x00, 0xf0, 0x03, 0xf0, 0x3f, 0x00,
0x00, 0x00, 0xf8, 0x03, 0xf8, 0x7f, 0x00, 0x00,
0x00, 0xf8, 0x01, 0xfc, 0x7e, 0x00, 0x00, 0x00,
0xfc, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0xfe,
0x00, 0x7e, 0xf8, 0x00, 0x00, 0x00, 0x7e, 0x00,
0x3f, 0xf8, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x1f,
0xf0, 0x01, 0x00, 0x00, 0x1f, 0x80, 0x1f, 0xe0,
0x03, 0x00, 0x80, 0x1f, 0xc0, 0x0f, 0xe0, 0x03,
0x00, 0x80, 0x0f, 0xc0, 0x07, 0xc0, 0x07, 0x00,
0xc0, 0x0f, 0xe0, 0x07, 0x80, 0x0f, 0x00, 0xe0,
0x07, 0xf0, 0x03, 0x80, 0x1f, 0x00, 0xe0, 0x03,
0xf8, 0x03, 0x00, 0x1f, 0x00, 0xf0, 0x03, 0xf8,
0x01, 0x00, 0x3f, 0x00, 0xf8, 0x01, 0xfc, 0x00,
0x00, 0x7e, 0x00, 0xfc, 0x00, 0xfe, 0x00, 0x00,
0x7e, 0x00, 0xfc, 0x00, 0x7e, 0x00, 0x00, 0xfc,
0x00, 0x7e, 0x00, 0x3f, 0x00, 0x00, 0xf8, 0x00,
0x7e, 0x00, 0x3e, 0x00, 0x00, 0xf8, 0x00, 0x38,
0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
#endif

View File

@@ -188,7 +188,7 @@ void ExpressLRSFiveWay::determineAction(KeyType key, PressLength length)
// Feed input to the canned messages module
void ExpressLRSFiveWay::sendKey(input_broker_event key)
{
InputEvent e = {};
InputEvent e;
e.source = inputSourceName;
e.inputEvent = key;
notifyObservers(&e);

View File

@@ -73,7 +73,7 @@ int32_t LinuxInput::runOnce()
int rd = read(events[i].data.fd, ev, sizeof(ev));
assert(rd > ((signed int)sizeof(struct input_event)));
for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) {
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName;
e.kbchar = 0;

View File

@@ -27,7 +27,7 @@ void RotaryEncoderInterruptBase::init(
if (!isRAK || pinPress != 0) {
pinMode(pinPress, INPUT_PULLUP);
attachInterrupt(pinPress, onIntPress, CHANGE);
attachInterrupt(pinPress, onIntPress, RISING);
}
if (!isRAK || this->_pinA != 0) {
pinMode(this->_pinA, INPUT_PULLUP);
@@ -45,7 +45,7 @@ void RotaryEncoderInterruptBase::init(
int32_t RotaryEncoderInterruptBase::runOnce()
{
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName;
unsigned long now = millis();
@@ -151,7 +151,7 @@ RotaryEncoderInterruptBaseStateType RotaryEncoderInterruptBase::intHandler(bool
// Logic to prevent bouncing.
newState = ROTARY_EVENT_CLEARED;
}
setIntervalFromNow(ROTARY_DELAY); // TODO: this modifies a non-volatile variable!
setIntervalFromNow(50); // TODO: this modifies a non-volatile variable!
return newState;
}

View File

@@ -49,7 +49,7 @@ bool SeesawRotary::init()
int32_t SeesawRotary::runOnce()
{
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
bool currentlyPressed = !ss.digitalRead(SS_SWITCH);

View File

@@ -29,7 +29,7 @@ SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name)
void SerialKeyboard::erase()
{
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_BACK;
e.kbchar = 0x08;
e.source = this->_originName;
@@ -80,7 +80,7 @@ int32_t SerialKeyboard::runOnce()
if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but
// shouldn't be a limitation
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName;
// SELECT OR SEND OR CANCEL EVENT

View File

@@ -47,7 +47,7 @@ bool TouchScreenImpl1::getTouch(int16_t &x, int16_t &y)
*/
void TouchScreenImpl1::onEvent(const TouchEvent &event)
{
InputEvent e = {};
InputEvent e;
e.source = event.source;
e.kbchar = 0;
e.touchX = event.x;

View File

@@ -51,7 +51,7 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
int32_t TrackballInterruptBase::runOnce()
{
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
// Handle long press detection for press button

View File

@@ -48,7 +48,7 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
int32_t UpDownInterruptBase::runOnce()
{
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
unsigned long now = millis();

View File

@@ -1,95 +0,0 @@
#include "i2cButton.h"
#include "meshUtils.h"
#include "configuration.h"
#if defined(M5STACK_UNITC6L)
#include "MeshService.h"
#include "RadioLibInterface.h"
#include "buzz.h"
#include "input/InputBroker.h"
#include "main.h"
#include "modules/CannedMessageModule.h"
#include "modules/ExternalNotificationModule.h"
#include "power.h"
#include "sleep.h"
#ifdef ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
#endif
i2cButtonThread *i2cButton;
using namespace concurrency;
extern void i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value);
extern void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value);
#define PI4IO_M_ADDR 0x43
#define getbit(x, y) ((x) >> (y)&0x01)
#define PI4IO_REG_IRQ_STA 0x13
#define PI4IO_REG_IN_STA 0x0F
#define PI4IO_REG_CHIP_RESET 0x01
i2cButtonThread::i2cButtonThread(const char *name) : OSThread(name)
{
_originName = name;
if (inputBroker)
inputBroker->registerSource(this);
}
int32_t i2cButtonThread::runOnce()
{
static bool btn1_pressed = false;
static uint32_t press_start_time = 0;
const uint32_t LONG_PRESS_TIME = 1000;
static bool long_press_triggered = false;
uint8_t in_data;
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, in_data);
if (getbit(in_data, 0)) {
uint8_t input_state;
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IN_STA, &input_state);
if (!getbit(input_state, 0)) {
if (!btn1_pressed) {
btn1_pressed = true;
press_start_time = millis();
long_press_triggered = false;
}
} else {
if (btn1_pressed) {
btn1_pressed = false;
uint32_t press_duration = millis() - press_start_time;
if (long_press_triggered) {
long_press_triggered = false;
return 50;
}
if (press_duration < LONG_PRESS_TIME) {
InputEvent evt;
evt.source = "UserButton";
evt.inputEvent = INPUT_BROKER_USER_PRESS;
evt.kbchar = 0;
evt.touchX = 0;
evt.touchY = 0;
this->notifyObservers(&evt);
}
}
}
}
if (btn1_pressed && !long_press_triggered && (millis() - press_start_time >= LONG_PRESS_TIME)) {
long_press_triggered = true;
InputEvent evt;
evt.source = "UserButton";
evt.inputEvent = INPUT_BROKER_SELECT;
evt.kbchar = 0;
evt.touchX = 0;
evt.touchY = 0;
this->notifyObservers(&evt);
}
return 50;
}
#endif

View File

@@ -1,18 +0,0 @@
#pragma once
#include "InputBroker.h"
#include "OneButton.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#if defined(M5STACK_UNITC6L)
class i2cButtonThread : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
const char *_originName;
explicit i2cButtonThread(const char *name);
int32_t runOnce() override;
};
extern i2cButtonThread *i2cButton;
#endif

View File

@@ -90,7 +90,7 @@ int32_t KbI2cBase::runOnce()
while (keyCount--) {
const BBQ10Keyboard::KeyEvent key = Q10keyboard.keyEvent();
if ((key.key != 0x00) && (key.state == BBQ10Keyboard::StateRelease)) {
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName;
switch (key.key) {
@@ -187,7 +187,7 @@ int32_t KbI2cBase::runOnce()
}
case 0x37: { // MPR121
MPRkeyboard.trigger();
InputEvent e = {};
InputEvent e;
while (MPRkeyboard.hasEvent()) {
char nextEvent = MPRkeyboard.dequeueEvent();
@@ -250,7 +250,7 @@ int32_t KbI2cBase::runOnce()
}
case 0x84: { // Adafruit TCA8418
TCAKeyboard.trigger();
InputEvent e = {};
InputEvent e;
while (TCAKeyboard.hasEvent()) {
char nextEvent = TCAKeyboard.dequeueEvent();
e.inputEvent = INPUT_BROKER_ANYKEY;
@@ -350,7 +350,7 @@ int32_t KbI2cBase::runOnce()
}
if (PrintDataBuf != 0) {
LOG_DEBUG("RAK14004 key 0x%x pressed", PrintDataBuf);
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_MATRIXKEY;
e.source = this->_originName;
e.kbchar = PrintDataBuf;
@@ -365,7 +365,7 @@ int32_t KbI2cBase::runOnce()
if (i2cBus->available()) {
char c = i2cBus->read();
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName;
switch (c) {

View File

@@ -72,7 +72,7 @@ int32_t KbMatrixBase::runOnce()
if (key != 0) {
LOG_DEBUG("Key 0x%x pressed", key);
// reset shift now that we have a keypress
InputEvent e = {};
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName;
switch (key) {

View File

@@ -387,6 +387,7 @@ void setup()
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
io.pinMode(EXPANDS_SD_PULLEN, INPUT);
#endif
concurrency::hasBeenSetup = true;
#if ARCH_PORTDUINO
SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
@@ -545,12 +546,6 @@ void setup()
#endif
#endif
#if defined(M5STACK_UNITC6L)
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, 1);
c6l_init();
#endif
#ifdef PIN_LCD_RESET
// FIXME - move this someplace better, LCD is at address 0x3F
pinMode(PIN_LCD_RESET, OUTPUT);
@@ -884,8 +879,7 @@ void setup()
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_SPISSD1306)
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) &&
@@ -1148,8 +1142,7 @@ void setup()
// Don't call screen setup until after nodedb is setup (because we need
// the current region name)
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_SPISSD1306)
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
if (screen)
screen->setup();
#elif defined(ARCH_PORTDUINO)
@@ -1513,6 +1506,9 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
deviceMetadata.hw_model = HW_VENDOR;
deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled;
deviceMetadata.excluded_modules = meshtastic_ExcludedModules_EXCLUDED_NONE;
#if MESHTASTIC_EXCLUDE_MQTT
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_MQTT_CONFIG;
#endif
#if MESHTASTIC_EXCLUDE_REMOTEHARDWARE
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_REMOTEHARDWARE_CONFIG;
#endif
@@ -1535,10 +1531,21 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
#if NO_EXT_GPIO && NO_GPS || MESHTASTIC_EXCLUDE_SERIAL
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_SERIAL_CONFIG;
#endif
#ifndef ARCH_ESP32
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_PAXCOUNTER
// PAXCOUNTER is only supported on ESP32 due to memory constraints
#else
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_PAXCOUNTER_CONFIG;
#endif
#if !defined(HAS_RGB_LED) && !RAK_4631
#if MESHTASTIC_EXCLUDE_STOREFORWARD
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_STOREFORWARD_CONFIG;
#endif
#if MESHTASTIC_EXCLUDE_RANGETEST
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_RANGETEST_CONFIG;
#endif
#if MESHTASTIC_EXCLUDE_NEIGHBORINFO
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_NEIGHBORINFO_CONFIG;
#endif
#if (!defined(HAS_RGB_LED) && !defined(RAK_4631)) || defined(MESHTASTIC_EXCLUDE_AMBIENTLIGHTING)
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_AMBIENTLIGHTING_CONFIG;
#endif

View File

@@ -88,16 +88,4 @@ uint32_t MemGet::getPsramSize()
#else
return 0;
#endif
}
void displayPercentHeapFree()
{
uint32_t freeHeap = memGet.getFreeHeap();
uint32_t totalHeap = memGet.getHeapSize();
if (totalHeap == 0 || totalHeap == UINT32_MAX) {
LOG_INFO("Heap size unavailable");
return;
}
int percent = (int)((freeHeap * 100) / totalHeap);
LOG_INFO("Heap free: %d%% (%u/%u bytes)", percent, freeHeap, totalHeap);
}

View File

@@ -423,33 +423,6 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
}
}
bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash)
{
// Iterate all known presets
for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
++preset) {
const char *name = DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false,
config.lora.use_preset);
if (!name)
continue;
if (strcmp(name, "Invalid") == 0)
continue; // skip invalid placeholder
uint8_t h = xorHash((const uint8_t *)name, strlen(name));
// Expand default PSK alias 1 to actual bytes and xor into hash
uint8_t tmp = h ^ xorHash(defaultpsk, sizeof(defaultpsk));
if (tmp == channelHash) {
// Set crypto to defaultpsk and report success
CryptoKey k;
memcpy(k.bytes, defaultpsk, sizeof(defaultpsk));
k.length = sizeof(defaultpsk);
crypto->setKey(k);
LOG_INFO("Matched default preset '%s' for hash 0x%x; set default PSK", name, channelHash);
return true;
}
}
return false;
}
/** Given a channel index setup crypto for encoding that channel (or the primary channel if that channel is unsecured)
*
* This method is called before encoding outbound packets

View File

@@ -94,8 +94,6 @@ class Channels
bool ensureLicensedOperation();
bool setDefaultPresetCryptoForHash(ChannelHash channelHash);
private:
/** Given a channel index, change to use the crypto key specified by that index
*

View File

@@ -43,30 +43,12 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
return Router::shouldFilterReceived(p);
}
bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
{
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
// ROUTER, REPEATER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
// even if we've heard another station rebroadcast it already.
return false;
}
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
// CLIENT_BASE: if the packet is from or to a favorited node,
// we should act like a ROUTER and should never cancel a rebroadcast (i.e. we should always rebroadcast),
// even if we've heard another station rebroadcast it already.
return !nodeDB->isFromOrToFavoritedNode(*p);
}
// All other roles (such as CLIENT) should cancel a rebroadcast if they hear another station's rebroadcast.
return true;
}
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
{
if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA && roleAllowsCancelingDupe(p)) {
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE &&
p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
// But only LoRa packets should be able to trigger this.
if (Router::cancelSending(p->from, p->id))

View File

@@ -59,10 +59,6 @@ class FloodingRouter : public Router
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
// Return false for roles like ROUTER or REPEATER which should always rebroadcast even when we've heard another rebroadcast of
// the same packet
bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
/* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */
void perhapsCancelDupe(const meshtastic_MeshPacket *p);

View File

@@ -6,7 +6,6 @@
#include <memory>
#include "PointerQueue.h"
#include "configuration.h" // For LOG_WARN, LOG_DEBUG, LOG_HEAP
template <class T> class Allocator
{
@@ -15,14 +14,13 @@ template <class T> class Allocator
Allocator() : deleter([this](T *p) { this->release(p); }) {}
virtual ~Allocator() {}
/// Return a queable object which has been prefilled with zeros. Return nullptr if no buffer is available
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
/// Note: this method is safe to call from regular OR ISR code
T *allocZeroed()
{
T *p = allocZeroed(0);
if (!p) {
LOG_WARN("Failed to allocate zeroed memory");
}
assert(p); // FIXME panic instead
return p;
}
@@ -41,12 +39,10 @@ template <class T> class Allocator
T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
{
T *p = alloc(maxWait);
if (!p) {
LOG_WARN("Failed to allocate memory for copy");
return nullptr;
}
assert(p);
*p = src;
if (p)
*p = src;
return p;
}
@@ -87,11 +83,7 @@ template <class T> class MemoryDynamic : public Allocator<T>
/// Return a buffer for use by others
virtual void release(T *p) override
{
if (p == nullptr)
return;
LOG_HEAP("Freeing 0x%x", p);
assert(p);
free(p);
}
@@ -104,58 +96,3 @@ template <class T> class MemoryDynamic : public Allocator<T>
return p;
}
};
/**
* A static memory pool that uses a fixed buffer instead of heap allocation
*/
template <class T, int MaxSize> class MemoryPool : public Allocator<T>
{
private:
T pool[MaxSize];
bool used[MaxSize];
public:
MemoryPool() : pool{}, used{}
{
// Arrays are now zero-initialized by member initializer list
// pool array: all elements are default-constructed (zero for POD types)
// used array: all elements are false (zero-initialized)
}
/// Return a buffer for use by others
virtual void release(T *p) override
{
if (!p) {
LOG_DEBUG("Failed to release memory, pointer is null");
return;
}
// Find the index of this pointer in our pool
int index = p - pool;
if (index >= 0 && index < MaxSize) {
assert(used[index]); // Should be marked as used
used[index] = false;
LOG_HEAP("Released static pool item %d at 0x%x", index, p);
} else {
LOG_WARN("Pointer 0x%x not from our pool!", p);
}
}
protected:
// Alloc some storage from our static pool
virtual T *alloc(TickType_t maxWait) override
{
// Find first free slot
for (int i = 0; i < MaxSize; i++) {
if (!used[i]) {
used[i] = true;
LOG_HEAP("Allocated static pool item %d at 0x%x", i, &pool[i]);
return &pool[i];
}
}
// No free slots available - return nullptr instead of asserting
LOG_WARN("No free slots available in static memory pool!");
return nullptr;
}
};

View File

@@ -100,6 +100,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
// Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
auto ourNodeNum = nodeDB->getNodeNum();
bool toUs = isBroadcast(mp.to) || isToUs(&mp);
bool fromUs = mp.from == ourNodeNum;
for (auto i = modules->begin(); i != modules->end(); ++i) {
auto &pi = **i;

View File

@@ -46,14 +46,11 @@ the new node can build its node db)
MeshService *service;
#define MAX_MQTT_PROXY_MESSAGES 16
static MemoryPool<meshtastic_MqttClientProxyMessage, MAX_MQTT_PROXY_MESSAGES> staticMqttClientProxyMessagePool;
static MemoryDynamic<meshtastic_MqttClientProxyMessage> staticMqttClientProxyMessagePool;
#define MAX_QUEUE_STATUS 4
static MemoryPool<meshtastic_QueueStatus, MAX_QUEUE_STATUS> staticQueueStatusPool;
static MemoryDynamic<meshtastic_QueueStatus> staticQueueStatusPool;
#define MAX_CLIENT_NOTIFICATIONS 4
static MemoryPool<meshtastic_ClientNotification, MAX_CLIENT_NOTIFICATIONS> staticClientNotificationPool;
static MemoryDynamic<meshtastic_ClientNotification> staticClientNotificationPool;
Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool;
@@ -64,10 +61,8 @@ Allocator<meshtastic_QueueStatus> &queueStatusPool = staticQueueStatusPool;
#include "Router.h"
MeshService::MeshService()
#ifdef ARCH_PORTDUINO
: toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_QUEUESTATUS_TOPHONE),
toPhoneMqttProxyQueue(MAX_RX_MQTTPROXY_TOPHONE), toPhoneClientNotificationQueue(MAX_RX_NOTIFICATION_TOPHONE)
#endif
: toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE),
toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2)
{
lastQueueStatus = {0, 0, 16, 0};
}
@@ -196,10 +191,8 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
// (so we update our nodedb for the local node)
// Send the packet into the mesh
DEBUG_HEAP_BEFORE;
auto a = packetPool.allocCopy(p);
DEBUG_HEAP_AFTER("MeshService::handleToRadio", a);
sendToMesh(a, RX_SRC_USER);
sendToMesh(packetPool.allocCopy(p), RX_SRC_USER);
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
if (loopback) {
@@ -255,11 +248,7 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh
}
if ((res == ERRNO_OK || res == ERRNO_SHOULD_RELEASE) && ccToPhone) { // Check if p is not released in case it couldn't be sent
DEBUG_HEAP_BEFORE;
auto a = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("MeshService::sendToMesh", a);
sendToPhone(a);
sendToPhone(packetPool.allocCopy(*p));
}
// Router may ask us to release the packet if it wasn't sent

View File

@@ -9,12 +9,7 @@
#include "MeshRadio.h"
#include "MeshTypes.h"
#include "Observer.h"
#ifdef ARCH_PORTDUINO
#include "PointerQueue.h"
#else
#include "StaticPointerQueue.h"
#endif
#include "mesh-pb-constants.h"
#if defined(ARCH_PORTDUINO)
#include "../platform/portduino/SimRadio.h"
#endif
@@ -42,32 +37,16 @@ class MeshService
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
/// we never hang because android hasn't been there in a while
/// FIXME - save this to flash on deep sleep
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_MeshPacket> toPhoneQueue;
#else
StaticPointerQueue<meshtastic_MeshPacket, MAX_RX_TOPHONE> toPhoneQueue;
#endif
// keep list of QueueStatus packets to be send to the phone
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_QueueStatus> toPhoneQueueStatusQueue;
#else
StaticPointerQueue<meshtastic_QueueStatus, MAX_RX_QUEUESTATUS_TOPHONE> toPhoneQueueStatusQueue;
#endif
// keep list of MqttClientProxyMessages to be send to the client for delivery
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_MqttClientProxyMessage> toPhoneMqttProxyQueue;
#else
StaticPointerQueue<meshtastic_MqttClientProxyMessage, MAX_RX_MQTTPROXY_TOPHONE> toPhoneMqttProxyQueue;
#endif
// keep list of ClientNotifications to be send to the client (phone)
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_ClientNotification> toPhoneClientNotificationQueue;
#else
StaticPointerQueue<meshtastic_ClientNotification, MAX_RX_NOTIFICATION_TOPHONE> toPhoneClientNotificationQueue;
#endif
// This holds the last QueueStatus send
meshtastic_QueueStatus lastQueueStatus;

View File

@@ -161,15 +161,6 @@ bool NextHopRouter::stopRetransmission(NodeNum from, PacketId id)
return stopRetransmission(key);
}
bool NextHopRouter::roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *p)
{
// Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
// Return false for roles like ROUTER, REPEATER, ROUTER_LATE which should always transmit the packet at least once.
return roleAllowsCancelingDupe(p); // same logic as FloodingRouter::roleAllowsCancelingDupe
}
bool NextHopRouter::stopRetransmission(GlobalPacketId key)
{
auto old = findPendingPacket(key);
@@ -179,21 +170,17 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
to avoid canceling a transmission if it was ACKed super fast via MQTT */
if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
// We only cancel it if we are the original sender or if we're not a router(_late)/repeater
if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) {
if (isFromUs(p) || (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id);
// now free the pooled copy for retransmission too
packetPool.release(p);
}
}
// Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
// get scheduled again. (This is the core of stopRetransmission.)
auto numErased = pending.erase(key);
assert(numErased == 1);
// When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
// to startRetransmission.
packetPool.release(p);
return true;
} else
return false;

View File

@@ -121,9 +121,6 @@ class NextHopRouter : public FloodingRouter
*/
PendingPacket *startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx = NUM_INTERMEDIATE_RETX);
// Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
bool roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *p);
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*

View File

@@ -663,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.bluetooth.fixed_pin = defaultBLEPin;
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306)
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
bool hasScreen = true;
#ifdef HELTEC_MESH_NODE_T114
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);
@@ -775,7 +775,9 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.version = DEVICESTATE_CUR_VER;
moduleConfig.has_mqtt = true;
#if !MESHTASTIC_EXCLUDE_RANGETEST
moduleConfig.has_range_test = true;
#endif
moduleConfig.has_serial = true;
moduleConfig.has_store_forward = true;
moduleConfig.has_telemetry = true;
@@ -841,6 +843,12 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.canned_message.inputbroker_event_press = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
#endif
moduleConfig.has_canned_message = true;
#if !MESHTASTIC_EXCLUDE_AUDIO
moduleConfig.has_audio = true;
#endif
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
moduleConfig.has_paxcounter = true;
#endif
#if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT
moduleConfig.mqtt.enabled = true;
#endif
@@ -883,12 +891,14 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.detection_sensor.detection_trigger_type = meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH;
moduleConfig.detection_sensor.minimum_broadcast_secs = 45;
#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
moduleConfig.has_ambient_lighting = true;
moduleConfig.ambient_lighting.current = 10;
// Default to a color based on our node number
moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16;
moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8;
moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF;
#endif
initModuleConfigIntervals();
}
@@ -1428,15 +1438,25 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
moduleConfig.has_canned_message = true;
moduleConfig.has_external_notification = true;
moduleConfig.has_mqtt = true;
#if !MESHTASTIC_EXCLUDE_RANGETEST
moduleConfig.has_range_test = true;
#endif
moduleConfig.has_serial = true;
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
moduleConfig.has_store_forward = true;
#endif
moduleConfig.has_telemetry = true;
moduleConfig.has_neighbor_info = true;
moduleConfig.has_detection_sensor = true;
#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
moduleConfig.has_ambient_lighting = true;
#endif
#if !MESHTASTIC_EXCLUDE_AUDIO
moduleConfig.has_audio = true;
#endif
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
moduleConfig.has_paxcounter = true;
#endif
success &=
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
@@ -1750,65 +1770,6 @@ void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
}
}
bool NodeDB::isFavorite(uint32_t nodeId)
{
// returns true if nodeId is_favorite; false if not or not found
// NODENUM_BROADCAST will never be in the DB
if (nodeId == NODENUM_BROADCAST)
return false;
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
if (lite) {
return lite->is_favorite;
}
return false;
}
bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
{
// This method is logically equivalent to:
// return isFavorite(p.from) || isFavorite(p.to);
// but is more efficient by:
// 1. doing only one pass through the database, instead of two
// 2. exiting early when a favorite is found, or if both from and to have been seen
if (p.to == NODENUM_BROADCAST)
return isFavorite(p.from); // we never store NODENUM_BROADCAST in the DB, so we only need to check p.from
meshtastic_NodeInfoLite *lite = NULL;
bool seenFrom = false;
bool seenTo = false;
for (int i = 0; i < numMeshNodes; i++) {
lite = &meshNodes->at(i);
if (lite->num == p.from) {
if (lite->is_favorite)
return true;
seenFrom = true;
}
if (lite->num == p.to) {
if (lite->is_favorite)
return true;
seenTo = true;
}
if (seenFrom && seenTo)
return false; // we've seen both, and neither is a favorite, so we can stop searching early
// Note: if we knew that sortMeshDB was always called after any change to is_favorite, we could exit early after searching
// all favorited nodes first.
}
return false;
}
void NodeDB::pause_sort(bool paused)
{
sortingIsPaused = paused;

View File

@@ -185,16 +185,6 @@ class NodeDB
*/
void set_favorite(bool is_favorite, uint32_t nodeId);
/*
* Returns true if the node is in the NodeDB and marked as favorite
*/
bool isFavorite(uint32_t nodeId);
/*
* Returns true if p->from or p->to is a favorited node
*/
bool isFromOrToFavoritedNode(const meshtastic_MeshPacket &p);
/**
* Other functions like the node picker can request a pause in the node sorting
*/

View File

@@ -34,6 +34,21 @@
// Flag to indicate a heartbeat was received and we should send queue status
bool heartbeatReceived = false;
// Helper function to skip excluded module configs and advance state
size_t PhoneAPI::skipExcludedModuleConfig(uint8_t *buf)
{
config_state++;
if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) {
if (config_nonce == SPECIAL_NONCE_ONLY_CONFIG) {
state = STATE_SEND_FILEMANIFEST;
} else {
state = STATE_SEND_OTHER_NODEINFOS;
}
config_state = 0;
}
return getFromRadio(buf);
}
PhoneAPI::PhoneAPI()
{
lastContactMsec = millis();
@@ -100,7 +115,6 @@ void PhoneAPI::close()
config_nonce = 0;
config_state = 0;
pauseBluetoothLogging = false;
heartbeatReceived = false;
}
}
@@ -355,20 +369,35 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.moduleConfig.payload_variant.serial = moduleConfig.serial;
break;
case meshtastic_ModuleConfig_external_notification_tag:
#if !(NO_EXT_GPIO || MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION)
LOG_DEBUG("Send module config: ext notification");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_external_notification_tag;
fromRadioScratch.moduleConfig.payload_variant.external_notification = moduleConfig.external_notification;
break;
#else
LOG_DEBUG("External Notification module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
case meshtastic_ModuleConfig_store_forward_tag:
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
LOG_DEBUG("Send module config: store forward");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_store_forward_tag;
fromRadioScratch.moduleConfig.payload_variant.store_forward = moduleConfig.store_forward;
break;
#else
LOG_DEBUG("Store & Forward module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
case meshtastic_ModuleConfig_range_test_tag:
#if !MESHTASTIC_EXCLUDE_RANGETEST
LOG_DEBUG("Send module config: range test");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_range_test_tag;
fromRadioScratch.moduleConfig.payload_variant.range_test = moduleConfig.range_test;
break;
#else
LOG_DEBUG("Range Test module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
case meshtastic_ModuleConfig_telemetry_tag:
LOG_DEBUG("Send module config: telemetry");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_telemetry_tag;
@@ -380,10 +409,15 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.moduleConfig.payload_variant.canned_message = moduleConfig.canned_message;
break;
case meshtastic_ModuleConfig_audio_tag:
#if !MESHTASTIC_EXCLUDE_AUDIO
LOG_DEBUG("Send module config: audio");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_audio_tag;
fromRadioScratch.moduleConfig.payload_variant.audio = moduleConfig.audio;
break;
#else
LOG_DEBUG("Audio module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
case meshtastic_ModuleConfig_remote_hardware_tag:
LOG_DEBUG("Send module config: remote hardware");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_remote_hardware_tag;
@@ -400,15 +434,25 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.moduleConfig.payload_variant.detection_sensor = moduleConfig.detection_sensor;
break;
case meshtastic_ModuleConfig_ambient_lighting_tag:
#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
LOG_DEBUG("Send module config: ambient lighting");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_ambient_lighting_tag;
fromRadioScratch.moduleConfig.payload_variant.ambient_lighting = moduleConfig.ambient_lighting;
break;
#else
LOG_DEBUG("Ambient Lighting module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
case meshtastic_ModuleConfig_paxcounter_tag:
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
LOG_DEBUG("Send module config: paxcounter");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
fromRadioScratch.moduleConfig.payload_variant.paxcounter = moduleConfig.paxcounter;
break;
#else
LOG_DEBUG("Paxcounter module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
default:
LOG_ERROR("Unknown module config type %d", config_state);
}

View File

@@ -172,4 +172,7 @@ class PhoneAPI
/// If the mesh service tells us fromNum has changed, tell the phone
virtual int onNotify(uint32_t newValue) override;
/// Helper function to skip excluded module configs and advance state
size_t skipExcludedModuleConfig(uint8_t *buf);
};

View File

@@ -314,33 +314,16 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
return (2 * CWmax * slotTimeMsec) + pow_of_2(CWsize) * slotTimeMsec;
}
/** Returns true if we should rebroadcast early like a ROUTER */
bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
{
// If we are a ROUTER or REPEATER, we always rebroadcast early
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
return true;
}
// If we are a CLIENT_BASE and the packet is from or to a favorited node, we should rebroadcast early
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
return nodeDB->isFromOrToFavoritedNode(*p);
}
return false;
}
/** The delay to use when we want to flood a message */
uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
{
// high SNR = large CW size (Long Delay)
// low SNR = small CW size (Short Delay)
float snr = p->rx_snr;
uint32_t delay = 0;
uint8_t CWsize = getCWsize(snr);
// LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize);
if (shouldRebroadcastEarlyLikeRouter(p)) {
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
delay = random(0, 2 * CWsize) * slotTimeMsec;
LOG_DEBUG("rx_snr found in packet. Router: setting tx delay:%d", delay);
} else {

View File

@@ -180,11 +180,8 @@ class RadioInterface
/** The worst-case SNR_based packet delay */
uint32_t getTxDelayMsecWeightedWorst(float snr);
/** Returns true if we should rebroadcast early like a ROUTER */
bool shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p);
/** The delay to use when we want to flood a message. Use a weighted scale based on SNR */
uint32_t getTxDelayMsecWeighted(meshtastic_MeshPacket *p);
uint32_t getTxDelayMsecWeighted(float snr);
/** If the packet is not already in the late rebroadcast window, move it there */
virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }

View File

@@ -310,7 +310,7 @@ void RadioLibInterface::setTransmitDelay()
// So we want to make sure the other side has had a chance to reconfigure its radio.
if (p->tx_after) {
unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p) : getTxDelayMsec();
unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p->rx_snr) : getTxDelayMsec();
unsigned long now = millis();
p->tx_after = min(max(p->tx_after + add_delay, now + add_delay), now + 2 * getTxDelayMsecWeightedWorst(p->rx_snr));
notifyLater(p->tx_after - now, TRANSMIT_DELAY_COMPLETED, false);
@@ -323,7 +323,7 @@ void RadioLibInterface::setTransmitDelay()
} else {
// If there is a SNR, start a timer scaled based on that SNR.
LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
startTransmitTimerRebroadcast(p);
startTransmitTimerSNR(p->rx_snr);
}
}
@@ -336,11 +336,11 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
}
}
void RadioLibInterface::startTransmitTimerRebroadcast(meshtastic_MeshPacket *p)
void RadioLibInterface::startTransmitTimerSNR(float snr)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.empty()) {
uint32_t delay = getTxDelayMsecWeighted(p);
uint32_t delay = getTxDelayMsecWeighted(snr);
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
}
}

View File

@@ -161,7 +161,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
* timer scaled to SNR of to be flooded packet
* @return Timestamp after which the packet may be sent
*/
void startTransmitTimerRebroadcast(meshtastic_MeshPacket *p);
void startTransmitTimerSNR(float snr);
void handleTransmitInterrupt();
void handleReceiveInterrupt();

View File

@@ -2,7 +2,6 @@
#include "Default.h"
#include "MeshTypes.h"
#include "configuration.h"
#include "memGet.h"
#include "mesh-pb-constants.h"
#include "modules/NodeInfoModule.h"
#include "modules/RoutingModule.h"
@@ -22,10 +21,8 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
if (p->hop_limit == 0) {
p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
}
DEBUG_HEAP_BEFORE;
auto copy = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("ReliableRouter::send", copy);
auto copy = packetPool.allocCopy(*p);
startRetransmission(copy, NUM_RELIABLE_RETX);
}
@@ -97,34 +94,27 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
if (isToUs(p)) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability)
if (!MeshModule::currentReply) {
if (p->want_ack) {
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
/* A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received
an implicit ACK already. If we received it directly or via NextHopRouter, only ACK with a hop limit of 0 to
make sure the other side stops retransmitting. */
if (!p->decoded.request_id && !p->decoded.reply_id) {
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
} else if ((p->hop_start > 0 && p->hop_start == p->hop_limit) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
}
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
if (p->want_ack) {
if (MeshModule::currentReply) {
LOG_DEBUG("Another module replied to this message, no need for 2nd ack");
} else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
// A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received an
// implicit ACK already. If we received it directly, only ACK with a hop limit of 0
if (!p->decoded.request_id)
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
} else {
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
}
} else if (p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum()) && p->hop_limit > 0) {
// No wantAck, but we need to ACK with hop limit of 0 if we were the next hop to stop their retransmissions
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
else if (p->hop_start > 0 && p->hop_start == p->hop_limit)
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
} else {
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
}
} else {
LOG_DEBUG("Another module replied to this message, no need for 2nd ack");
}
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && c &&
c->error_reason == meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY) {

View File

@@ -5,7 +5,6 @@
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "configuration.h"
#include "detect/LoRaRadioType.h"
#include "main.h"
@@ -28,24 +27,14 @@
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
// And every TX packet might have a retransmission packet or an ack alive at any moment
#ifdef ARCH_PORTDUINO
// Portduino (native) targets can use dynamic memory pools with runtime-configurable sizes
#define MAX_PACKETS \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
#else
// Embedded targets use static memory pools with compile-time constants
#define MAX_PACKETS_STATIC \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
// static MemoryPool<MeshPacket> staticPool(MAX_PACKETS);
static MemoryDynamic<meshtastic_MeshPacket> staticPool;
static MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
#endif
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
@@ -286,10 +275,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
// If the packet is not yet encrypted, do so now
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("Router::send", p_decoded);
auto encodeResult = perhapsEncode(p);
if (encodeResult != meshtastic_Routing_Error_NONE) {
@@ -430,36 +416,6 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
}
}
}
#if HAS_UDP_MULTICAST
// Fallback: for UDP multicast, try default preset names with default PSK if normal channel match failed
if (!decrypted && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP) {
if (channels.setDefaultPresetCryptoForHash(p->channel)) {
memcpy(bytes, p->encrypted.bytes, rawSize);
crypto->decrypt(p->from, p->id, rawSize, bytes);
meshtastic_Data decodedtmp;
memset(&decodedtmp, 0, sizeof(decodedtmp));
if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) &&
decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) {
p->decoded = decodedtmp;
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
// Map to our local default channel index (name+PSK default), not necessarily primary
ChannelIndex defaultIndex = channels.getPrimaryIndex();
for (ChannelIndex i = 0; i < channels.getNumChannels(); ++i) {
if (channels.isDefaultChannel(i)) {
defaultIndex = i;
break;
}
}
chIndex = defaultIndex;
decrypted = true;
} else {
LOG_WARN("UDP fallback decode attempted but failed for hash 0x%x", p->channel);
}
}
}
#endif
if (decrypted) {
// parsing was successful
p->channel = chIndex; // change to store the index instead of the hash
@@ -651,11 +607,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
bool skipHandle = false;
// Also, we should set the time from the ISR and it should have msec level resolution
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
// Store a copy of encrypted packet for MQTT
DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted);
// Take those raw bytes and convert them back into a well structured protobuf we can understand
auto decodedState = perhapsDecode(p);
@@ -703,7 +656,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
// call modules here
// If this could be a spoofed packet, don't let the modules see it.
if (!skipHandle) {
if (!skipHandle && p->from != nodeDB->getNodeNum()) {
MeshModule::callModules(*p, src);
#if !MESHTASTIC_EXCLUDE_MQTT
@@ -717,6 +670,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
!isFromUs(p) && mqtt)
mqtt->onSend(*p_encrypted, *p, p->channel);
#endif
} else if (p->from == nodeDB->getNodeNum() && !skipHandle) {
MeshModule::callModules(*p, src);
}
packetPool.release(p_encrypted); // Release the encrypted packet

View File

@@ -52,16 +52,6 @@ template <typename T> bool SX126xInterface<T>::init()
pinMode(SX126X_POWER_EN, OUTPUT);
#endif
#ifdef HELTEC_V4
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
pinMode(LORA_PA_EN, OUTPUT);
digitalWrite(LORA_PA_EN, LOW);
pinMode(LORA_PA_TX_EN, OUTPUT);
digitalWrite(LORA_PA_TX_EN, LOW);
#endif
#if ARCH_PORTDUINO
tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) {
@@ -73,7 +63,7 @@ template <typename T> bool SX126xInterface<T>::init()
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
else
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
setTransmitEnable(false);
// FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option
bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
@@ -269,7 +259,6 @@ template <typename T> void SX126xInterface<T>::addReceiveMetadata(meshtastic_Mes
*/
template <typename T> void SX126xInterface<T>::configHardwareForSend()
{
setTransmitEnable(true);
RadioLibInterface::configHardwareForSend();
}
@@ -282,7 +271,6 @@ template <typename T> void SX126xInterface<T>::startReceive()
sleep();
#else
setTransmitEnable(false);
setStandby();
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
@@ -310,7 +298,7 @@ template <typename T> bool SX126xInterface<T>::isChannelActive()
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
int16_t result;
setTransmitEnable(false);
setStandby();
result = lora.scanChannel(cfg);
if (result == RADIOLIB_LORA_DETECTED)
@@ -349,26 +337,6 @@ template <typename T> bool SX126xInterface<T>::sleep()
digitalWrite(SX126X_POWER_EN, LOW);
#endif
#ifdef HELTEC_V4
/*
* Do not switch the power on and off frequently.
* After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
* // digitalWrite(LORA_PA_POWER, LOW);
*/
digitalWrite(LORA_PA_EN, LOW);
digitalWrite(LORA_PA_TX_EN, LOW);
#endif
return true;
}
/** Some boards require GPIO control of tx vs rx paths */
template <typename T> void SX126xInterface<T>::setTransmitEnable(bool txon)
{
#ifdef HELTEC_V4
digitalWrite(LORA_PA_POWER, HIGH);
digitalWrite(LORA_PA_EN, HIGH);
digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);
#endif
}
#endif

View File

@@ -71,9 +71,5 @@ template <class T> class SX126xInterface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override;
private:
/** Some boards require GPIO control of tx vs rx paths */
void setTransmitEnable(bool txon);
};
#endif

View File

@@ -1,77 +0,0 @@
#pragma once
#include "concurrency/OSThread.h"
#include "freertosinc.h"
#include <cassert>
/**
* A static circular buffer queue for pointers.
* This provides the same interface as PointerQueue but uses a statically allocated
* buffer instead of dynamic allocation.
*/
template <class T, int MaxElements> class StaticPointerQueue
{
static_assert(MaxElements > 0, "MaxElements must be greater than 0");
T *buffer[MaxElements];
int head = 0;
int tail = 0;
int count = 0;
concurrency::OSThread *reader = nullptr;
public:
StaticPointerQueue()
{
// Initialize all buffer elements to nullptr to silence warnings and ensure clean state
for (int i = 0; i < MaxElements; i++) {
buffer[i] = nullptr;
}
}
int numFree() const { return MaxElements - count; }
bool isEmpty() const { return count == 0; }
int numUsed() const { return count; }
bool enqueue(T *x, TickType_t maxWait = portMAX_DELAY)
{
if (count >= MaxElements) {
return false; // Queue is full
}
if (reader) {
reader->setInterval(0);
concurrency::mainDelay.interrupt();
}
buffer[tail] = x;
tail = (tail + 1) % MaxElements;
count++;
return true;
}
bool dequeue(T **p, TickType_t maxWait = portMAX_DELAY)
{
if (count == 0) {
return false; // Queue is empty
}
*p = buffer[head];
head = (head + 1) % MaxElements;
count--;
return true;
}
// returns a ptr or null if the queue was empty
T *dequeuePtr(TickType_t maxWait = portMAX_DELAY)
{
T *p;
return dequeue(&p, maxWait) ? p : nullptr;
}
void setReader(concurrency::OSThread *t) { reader = t; }
// For compatibility with PointerQueue interface
int getMaxLen() const { return MaxElements; }
};

View File

@@ -50,15 +50,15 @@ class StreamAPI : public PhoneAPI
* phone.
*/
virtual int32_t runOncePart();
virtual int32_t runOncePart(char *buf, uint16_t bufLen);
virtual int32_t runOncePart(char *buf,uint16_t bufLen);
private:
/**
* Read any rx chars from the link and call handleToRadio
*/
int32_t readStream();
int32_t readStream(char *buf, uint16_t bufLen);
int32_t handleRecStream(char *buf, uint16_t bufLen);
int32_t readStream(char *buf,uint16_t bufLen);
int32_t handleRecStream(char *buf,uint16_t bufLen);
/**
* call getFromRadio() and deliver encapsulated packets to the Stream

View File

@@ -173,10 +173,28 @@ typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags {
meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1
} meshtastic_Config_NetworkConfig_ProtocolFlags;
/* Deprecated in 2.7.4: Unused */
typedef enum _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat {
meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED = 0
} meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat;
/* How the GPS coordinates are displayed on the OLED screen. */
typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat {
/* GPS coordinates are displayed in the normal decimal degrees format:
DD.DDDDDD DDD.DDDDDD */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC = 0,
/* GPS coordinates are displayed in the degrees minutes seconds format:
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS = 1,
/* Universal Transverse Mercator format:
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM = 2,
/* Military Grid Reference System format:
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
E is easting, N is northing */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS = 3,
/* Open Location Code (aka Plus Codes). */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC = 4,
/* Ordnance Survey Grid Reference (the National Grid System of the UK).
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
E is the easting, N is the northing */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR = 5
} meshtastic_Config_DisplayConfig_GpsCoordinateFormat;
/* Unit display preference */
typedef enum _meshtastic_Config_DisplayConfig_DisplayUnits {
@@ -473,7 +491,7 @@ typedef struct _meshtastic_Config_DisplayConfig {
uint32_t screen_on_secs;
/* Deprecated in 2.7.4: Unused
How the GPS coordinates are formatted on the OLED screen. */
meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat gps_format;
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. */
uint32_t auto_screen_carousel_secs;
@@ -497,9 +515,6 @@ typedef struct _meshtastic_Config_DisplayConfig {
/* If false (default), the device will display the time in 24-hour format on screen.
If true, the device will display the time in 12-hour format on screen. */
bool use_12h_clock;
/* If false (default), the device will use short names for various display screens.
If true, node names will show in long format */
bool use_long_node_name;
} meshtastic_Config_DisplayConfig;
/* Lora Config */
@@ -663,9 +678,9 @@ extern "C" {
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1))
#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat)(meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED+1))
#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC
#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR
#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1))
#define _meshtastic_Config_DisplayConfig_DisplayUnits_MIN meshtastic_Config_DisplayConfig_DisplayUnits_METRIC
#define _meshtastic_Config_DisplayConfig_DisplayUnits_MAX meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL
@@ -706,7 +721,7 @@ extern "C" {
#define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode
#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat
#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_GpsCoordinateFormat
#define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits
#define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType
#define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode
@@ -727,7 +742,7 @@ extern "C" {
#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0}
#define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -738,7 +753,7 @@ extern "C" {
#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0}
#define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -805,7 +820,6 @@ extern "C" {
#define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
#define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
#define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
#define meshtastic_Config_DisplayConfig_use_long_node_name_tag 13
#define meshtastic_Config_LoRaConfig_use_preset_tag 1
#define meshtastic_Config_LoRaConfig_modem_preset_tag 2
#define meshtastic_Config_LoRaConfig_bandwidth_tag 3
@@ -951,8 +965,7 @@ X(a, STATIC, SINGULAR, UENUM, displaymode, 8) \
X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \
X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \
X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11) \
X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12) \
X(a, STATIC, SINGULAR, BOOL, use_long_node_name, 13)
X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12)
#define meshtastic_Config_DisplayConfig_CALLBACK NULL
#define meshtastic_Config_DisplayConfig_DEFAULT NULL
@@ -1030,7 +1043,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
#define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
#define meshtastic_Config_BluetoothConfig_size 10
#define meshtastic_Config_DeviceConfig_size 100
#define meshtastic_Config_DisplayConfig_size 34
#define meshtastic_Config_DisplayConfig_size 32
#define meshtastic_Config_LoRaConfig_size 85
#define meshtastic_Config_NetworkConfig_IpV4Config_size 20
#define meshtastic_Config_NetworkConfig_size 204

View File

@@ -28,5 +28,3 @@ PB_BIND(meshtastic_Map, meshtastic_Map, AUTO)

View File

@@ -66,40 +66,12 @@ typedef enum _meshtastic_Language {
meshtastic_Language_UKRAINIAN = 16,
/* Bulgarian */
meshtastic_Language_BULGARIAN = 17,
/* Czech */
meshtastic_Language_CZECH = 18,
/* Simplified Chinese (experimental) */
meshtastic_Language_SIMPLIFIED_CHINESE = 30,
/* Traditional Chinese (experimental) */
meshtastic_Language_TRADITIONAL_CHINESE = 31
} meshtastic_Language;
/* How the GPS coordinates are displayed on the OLED screen. */
typedef enum _meshtastic_DeviceUIConfig_GpsCoordinateFormat {
/* GPS coordinates are displayed in the normal decimal degrees format:
DD.DDDDDD DDD.DDDDDD */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC = 0,
/* GPS coordinates are displayed in the degrees minutes seconds format:
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS = 1,
/* Universal Transverse Mercator format:
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM = 2,
/* Military Grid Reference System format:
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
E is easting, N is northing */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS = 3,
/* Open Location Code (aka Plus Codes). */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC = 4,
/* Ordnance Survey Grid Reference (the National Grid System of the UK).
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
E is the easting, N is the northing */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR = 5,
/* Maidenhead Locator System
Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS = 6
} meshtastic_DeviceUIConfig_GpsCoordinateFormat;
/* Struct definitions */
typedef struct _meshtastic_NodeFilter {
/* Filter unknown nodes */
@@ -189,8 +161,6 @@ typedef struct _meshtastic_DeviceUIConfig {
/* Clockface analog style
true for analog clockface, false for digital clockface */
bool is_clockface_analog;
/* How the GPS coordinates are formatted on the OLED screen. */
meshtastic_DeviceUIConfig_GpsCoordinateFormat gps_format;
} meshtastic_DeviceUIConfig;
@@ -211,14 +181,9 @@ extern "C" {
#define _meshtastic_Language_MAX meshtastic_Language_TRADITIONAL_CHINESE
#define _meshtastic_Language_ARRAYSIZE ((meshtastic_Language)(meshtastic_Language_TRADITIONAL_CHINESE+1))
#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC
#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MAX meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS
#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_DeviceUIConfig_GpsCoordinateFormat)(meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS+1))
#define meshtastic_DeviceUIConfig_theme_ENUMTYPE meshtastic_Theme
#define meshtastic_DeviceUIConfig_language_ENUMTYPE meshtastic_Language
#define meshtastic_DeviceUIConfig_compass_mode_ENUMTYPE meshtastic_CompassMode
#define meshtastic_DeviceUIConfig_gps_format_ENUMTYPE meshtastic_DeviceUIConfig_GpsCoordinateFormat
@@ -226,12 +191,12 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_DeviceUIConfig_init_default {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
#define meshtastic_DeviceUIConfig_init_default {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0}
#define meshtastic_NodeFilter_init_default {0, 0, 0, 0, 0, "", 0}
#define meshtastic_NodeHighlight_init_default {0, 0, 0, 0, ""}
#define meshtastic_GeoPoint_init_default {0, 0, 0}
#define meshtastic_Map_init_default {false, meshtastic_GeoPoint_init_default, "", 0}
#define meshtastic_DeviceUIConfig_init_zero {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
#define meshtastic_DeviceUIConfig_init_zero {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0}
#define meshtastic_NodeFilter_init_zero {0, 0, 0, 0, 0, "", 0}
#define meshtastic_NodeHighlight_init_zero {0, 0, 0, 0, ""}
#define meshtastic_GeoPoint_init_zero {0, 0, 0}
@@ -274,7 +239,6 @@ extern "C" {
#define meshtastic_DeviceUIConfig_compass_mode_tag 16
#define meshtastic_DeviceUIConfig_screen_rgb_color_tag 17
#define meshtastic_DeviceUIConfig_is_clockface_analog_tag 18
#define meshtastic_DeviceUIConfig_gps_format_tag 19
/* Struct field encoding specification for nanopb */
#define meshtastic_DeviceUIConfig_FIELDLIST(X, a) \
@@ -295,8 +259,7 @@ X(a, STATIC, SINGULAR, BYTES, calibration_data, 14) \
X(a, STATIC, OPTIONAL, MESSAGE, map_data, 15) \
X(a, STATIC, SINGULAR, UENUM, compass_mode, 16) \
X(a, STATIC, SINGULAR, UINT32, screen_rgb_color, 17) \
X(a, STATIC, SINGULAR, BOOL, is_clockface_analog, 18) \
X(a, STATIC, SINGULAR, UENUM, gps_format, 19)
X(a, STATIC, SINGULAR, BOOL, is_clockface_analog, 18)
#define meshtastic_DeviceUIConfig_CALLBACK NULL
#define meshtastic_DeviceUIConfig_DEFAULT NULL
#define meshtastic_DeviceUIConfig_node_filter_MSGTYPE meshtastic_NodeFilter
@@ -353,7 +316,7 @@ extern const pb_msgdesc_t meshtastic_Map_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size
#define meshtastic_DeviceUIConfig_size 204
#define meshtastic_DeviceUIConfig_size 201
#define meshtastic_GeoPoint_size 33
#define meshtastic_Map_size 58
#define meshtastic_NodeFilter_size 47

View File

@@ -360,7 +360,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
/* Maximum encoded size of messages (where known) */
/* meshtastic_NodeDatabase_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
#define meshtastic_BackupPreferences_size 2277
#define meshtastic_BackupPreferences_size 2273
#define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1737
#define meshtastic_NodeInfoLite_size 196

View File

@@ -187,8 +187,8 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
#define meshtastic_LocalConfig_size 749
#define meshtastic_LocalModuleConfig_size 673
#define meshtastic_LocalConfig_size 747
#define meshtastic_LocalModuleConfig_size 671
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_T_DECK_PRO = 102,
/* Lilygo TLora Pager */
meshtastic_HardwareModel_T_LORA_PAGER = 103,
/* M5Stack Reserved */
meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
/* GAT562 Mesh Trial Tracker */
meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
/* RAKwireless WisMesh Tag */
meshtastic_HardwareModel_WISMESH_TAG = 105,
/* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -274,10 +274,6 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_T_ECHO_LITE = 109,
/* New Heltec LoRA32 with ESP32-S3 CPU */
meshtastic_HardwareModel_HELTEC_V4 = 110,
/* M5Stack C6L */
meshtastic_HardwareModel_M5STACK_C6L = 111,
/* M5Stack Cardputer Adv */
meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
/* ------------------------------------------------------------------------------------------------------------------------------------------
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.
------------------------------------------------------------------------------------------------------------------------------------------ */

View File

@@ -356,9 +356,6 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig {
uint32_t health_update_interval;
/* Enable/Disable the health telemetry module on-device display */
bool health_screen_enabled;
/* Enable/Disable the device telemetry module to send metrics to the mesh
Note: We will still send telemtry to the connected phone / client every minute over the API */
bool device_telemetry_enabled;
} meshtastic_ModuleConfig_TelemetryConfig;
/* Canned Messages Module Config */
@@ -526,7 +523,7 @@ extern "C" {
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0, 0}
#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
#define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -542,7 +539,7 @@ extern "C" {
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0, 0}
#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0}
#define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -630,7 +627,6 @@ extern "C" {
#define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11
#define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12
#define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13
#define meshtastic_ModuleConfig_TelemetryConfig_device_telemetry_enabled_tag 14
#define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1
#define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2
#define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3
@@ -829,8 +825,7 @@ X(a, STATIC, SINGULAR, UINT32, power_update_interval, 9) \
X(a, STATIC, SINGULAR, BOOL, power_screen_enabled, 10) \
X(a, STATIC, SINGULAR, BOOL, health_measurement_enabled, 11) \
X(a, STATIC, SINGULAR, UINT32, health_update_interval, 12) \
X(a, STATIC, SINGULAR, BOOL, health_screen_enabled, 13) \
X(a, STATIC, SINGULAR, BOOL, device_telemetry_enabled, 14)
X(a, STATIC, SINGULAR, BOOL, health_screen_enabled, 13)
#define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL
@@ -915,7 +910,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
#define meshtastic_ModuleConfig_SerialConfig_size 28
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24
#define meshtastic_ModuleConfig_TelemetryConfig_size 48
#define meshtastic_ModuleConfig_TelemetryConfig_size 46
#define meshtastic_ModuleConfig_size 227
#define meshtastic_RemoteHardwarePin_size 21

View File

@@ -55,12 +55,12 @@ HTTPClient httpClient;
// We need to specify some content-type mapping, so the resources get delivered with the
// right content type and are displayed correctly in the browser
char const *contentTypes[][2] = {{".txt", "text/plain"}, {".html", "text/html"},
{".js", "text/javascript"}, {".png", "image/png"},
{".jpg", "image/jpg"}, {".gz", "application/gzip"},
{".gif", "image/gif"}, {".json", "application/json"},
{".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
{".svg", "image/svg+xml"}, {"", ""}};
char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"},
{".js", "text/javascript"}, {".png", "image/png"},
{".jpg", "image/jpg"}, {".gz", "application/gzip"},
{".gif", "image/gif"}, {".json", "application/json"},
{".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
{".svg", "image/svg+xml"}, {"", ""}};
// const char *certificate = NULL; // change this as needed, leave as is for no TLS check (yolo security)
@@ -292,14 +292,11 @@ JSONArray htmlListDir(const char *dirname, uint8_t levels)
JSONObject thisFileMap;
thisFileMap["size"] = new JSONValue((int)file.size());
#ifdef ARCH_ESP32
String fileName = String(file.path()).substring(1);
thisFileMap["name"] = new JSONValue(fileName.c_str());
thisFileMap["name"] = new JSONValue(String(file.path()).substring(1).c_str());
#else
String fileName = String(file.name()).substring(1);
thisFileMap["name"] = new JSONValue(fileName.c_str());
thisFileMap["name"] = new JSONValue(String(file.name()).substring(1).c_str());
#endif
String tempName = String(file.name()).substring(1);
if (tempName.endsWith(".gz")) {
if (String(file.name()).substring(1).endsWith(".gz")) {
#ifdef ARCH_ESP32
String modifiedFile = String(file.path()).substring(1);
#else
@@ -342,8 +339,7 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
@@ -371,8 +367,7 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
return;
} else {
@@ -381,8 +376,7 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("Error");
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
return;
}
@@ -628,7 +622,10 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
tempArray.push_back(new JSONValue((int)logArray[i]));
}
JSONValue *result = new JSONValue(tempArray);
// Note: Don't delete tempArray elements here - JSONValue now owns them
// Clean up original array to prevent memory leak
for (auto *val : tempArray) {
delete val;
}
return result;
};
@@ -659,9 +656,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
// data->wifi
JSONObject jsonObjWifi;
jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI());
String wifiIPString = WiFi.localIP().toString();
std::string wifiIP = wifiIPString.c_str();
jsonObjWifi["ip"] = new JSONValue(wifiIP.c_str());
jsonObjWifi["ip"] = new JSONValue(WiFi.localIP().toString().c_str());
// data->memory
JSONObject jsonObjMemory;
@@ -707,8 +702,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
}
@@ -779,8 +773,7 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
// Clean up the nodesArray to prevent memory leak
@@ -933,8 +926,7 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
}
@@ -976,8 +968,7 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
res->print(value->Stringify().c_str());
delete value;
// Clean up the networkObjs to prevent memory leak

View File

@@ -15,27 +15,8 @@
// FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in
// RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
#ifndef MAX_RX_TOPHONE
#if defined(ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3))
#define MAX_RX_TOPHONE 8
#else
#define MAX_RX_TOPHONE 32
#endif
#endif
/// max number of QueueStatus packets which can be waiting for delivery to phone
#ifndef MAX_RX_QUEUESTATUS_TOPHONE
#define MAX_RX_QUEUESTATUS_TOPHONE 2
#endif
/// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone
#ifndef MAX_RX_MQTTPROXY_TOPHONE
#define MAX_RX_MQTTPROXY_TOPHONE 8
#endif
/// max number of ClientNotification packets which can be waiting for delivery to phone
#ifndef MAX_RX_NOTIFICATION_TOPHONE
#define MAX_RX_NOTIFICATION_TOPHONE 2
#endif
/// Verify baseline assumption of node size. If it increases, we need to reevaluate
/// the impact of its memory footprint, notably on MAX_NUM_NODES.

View File

@@ -50,10 +50,10 @@ class UdpMulticastHandler final
LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength);
#endif
meshtastic_MeshPacket mp;
mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetLength);
bool isPacketDecoded = pb_decode_from_bytes(packet.data(), packetLength, &meshtastic_MeshPacket_msg, &mp);
if (isPacketDecoded && router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
mp.pki_encrypted = false;
mp.public_key.size = 0;
memset(mp.public_key.bytes, 0, sizeof(mp.public_key.bytes));

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