Compare commits

..

1 Commits

Author SHA1 Message Date
Ben Meadors
c89cc1da44 AGC reset on interval 2025-09-26 06:05:23 -05:00
243 changed files with 2647 additions and 4010 deletions

View File

@@ -19,8 +19,6 @@ jobs:
pio-build:
name: build-${{ inputs.platform }}
runs-on: ubuntu-24.04
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
- uses: actions/checkout@v5
with:
@@ -57,7 +55,6 @@ jobs:
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
id: upload
with:
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
overwrite: true

View File

@@ -3,7 +3,6 @@ name: Build One Arch
on:
workflow_dispatch:
inputs:
# trunk-ignore(checkov/CKV_GHA_7)
arch:
type: choice
options:
@@ -17,13 +16,10 @@ on:
- stm32
- native
permissions: read-all
env:
INPUT_ARCH: ${{ github.event.inputs.arch }}
jobs:
setup:
strategy:
fail-fast: false
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
@@ -35,11 +31,23 @@ jobs:
- name: Generate matrix
id: jsonStep
run: |
TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra)
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT
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:
selected_arch: ${{ steps.jsonStep.outputs.selected_arch }}
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
@@ -56,18 +64,101 @@ jobs:
long: ${{ steps.version.outputs.long }}
deb: ${{ steps.version.outputs.deb }}
build:
if: ${{ github.event_name != 'workflow_dispatch' }}
build-esp32:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix:
build: ${{ fromJson(needs.setup.outputs.selected_arch) }}
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.build.board }}
platform: ${{ matrix.build.arch }}
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' }}
@@ -88,6 +179,62 @@ jobs:
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
@@ -105,7 +252,18 @@ jobs:
- rp2350
- stm32
runs-on: ubuntu-latest
needs: [version, build]
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
@@ -174,3 +332,169 @@ jobs:
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

@@ -3,7 +3,6 @@ name: Build One Target
on:
workflow_dispatch:
inputs:
# trunk-ignore(checkov/CKV_GHA_7)
arch:
type: choice
options:
@@ -20,13 +19,11 @@ on:
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'
permissions: read-all
jobs:
find-targets:
if: ${{ inputs.target == '' }}
@@ -54,13 +51,13 @@ jobs:
- name: Generate matrix
id: jsonStep
run: |
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra)
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 >> $GITHUB_STEP_SUMMARY
echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY
version:
if: ${{ inputs.target != '' }}
@@ -78,9 +75,11 @@ jobs:
long: ${{ steps.version.outputs.long }}
deb: ${{ steps.version.outputs.deb }}
build:
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 }}
@@ -106,12 +105,70 @@ jobs:
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]
needs: [version, build-arch]
steps:
- name: Checkout code
uses: actions/checkout@v5
@@ -180,3 +237,159 @@ jobs:
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

@@ -27,11 +27,19 @@ on:
jobs:
setup:
if: github.repository == 'meshtastic/firmware'
strategy:
fail-fast: true
fail-fast: false
matrix:
arch:
- all
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- check
runs-on: ubuntu-24.04
steps:
@@ -41,22 +49,33 @@ jobs:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Uncomment build epoch
shell: bash
run: |
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
- 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}} --level pr)
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
echo "$TARGETS" >> $GITHUB_STEP_SUMMARY
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:
all: ${{ steps.jsonStep.outputs.all }}
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:
if: github.repository == 'meshtastic/firmware'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
@@ -75,8 +94,7 @@ jobs:
needs: setup
strategy:
fail-fast: false
matrix:
check: ${{ fromJson(needs.setup.outputs.check) }}
matrix: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
@@ -85,20 +103,96 @@ jobs:
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Check ${{ matrix.check.board }}
run: bin/check-all.sh ${{ matrix.check.board }}
- name: Check ${{ matrix.board }}
run: bin/check-all.sh ${{ matrix.board }}
build:
build-esp32:
needs: [setup, version]
strategy:
fail-fast: false
matrix:
build: ${{ fromJson(needs.setup.outputs.all) }}
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.build.board }}
platform: ${{ matrix.build.platform }}
pio_env: ${{ matrix.board }}
platform: esp32
build-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:
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:
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:
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:
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:
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:
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'
@@ -109,7 +203,7 @@ jobs:
secrets: inherit
package-pio-deps-native-tft:
if: ${{ github.repository == 'meshtastic/firmware' && github.event_name == 'workflow_dispatch' }}
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
@@ -119,26 +213,60 @@ jobs:
if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }}
uses: ./.github/workflows/test_native.yml
docker:
strategy:
fail-fast: false
matrix:
distro: [debian, alpine]
platform: [linux/amd64, linux/arm64, linux/arm/v7]
pio_env: [native, native-tft]
exclude:
- distro: alpine
platform: linux/arm/v7
- pio_env: native-tft
platform: linux/arm64
- pio_env: native-tft
platform: linux/arm/v7
docker-deb-amd64:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml
with:
distro: ${{ matrix.distro }}
platform: ${{ matrix.platform }}
runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
pio_env: ${{ matrix.pio_env }}
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-deb-amd64-tft:
if: github.repository == 'meshtastic/firmware'
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.repository == 'meshtastic/firmware'
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.repository == 'meshtastic/firmware'
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.repository == 'meshtastic/firmware'
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.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm/v7
runs-on: ubuntu-24.04-arm
push: false
gather-artifacts:
@@ -160,7 +288,18 @@ jobs:
- rp2350
- stm32
runs-on: ubuntu-latest
needs: [version, build]
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

View File

@@ -7,13 +7,23 @@ 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:
- all
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- check
runs-on: ubuntu-24.04
steps:
@@ -29,12 +39,19 @@ jobs:
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
else
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
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:
all: ${{ steps.jsonStep.outputs.all }}
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:
@@ -56,8 +73,7 @@ jobs:
needs: setup
strategy:
fail-fast: true
matrix:
check: ${{ fromJson(needs.setup.outputs.check) }}
matrix: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }}
@@ -66,19 +82,96 @@ jobs:
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Check ${{ matrix.check.board }}
run: bin/check-all.sh ${{ matrix.check.board }}
- name: Check ${{ matrix.board }}
run: bin/check-all.sh ${{ matrix.board }}
build:
build-esp32:
needs: [setup, version]
strategy:
matrix:
build: ${{ fromJson(needs.setup.outputs.all) }}
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.build.board }}
platform: ${{ matrix.build.platform }}
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'
@@ -99,26 +192,54 @@ jobs:
if: ${{ !contains(github.ref_name, 'event/') }}
uses: ./.github/workflows/test_native.yml
docker:
strategy:
fail-fast: false
matrix:
distro: [debian, alpine]
platform: [linux/amd64, linux/arm64, linux/arm/v7]
pio_env: [native, native-tft]
exclude:
- distro: alpine
platform: linux/arm/v7
- pio_env: native-tft
platform: linux/arm64
- pio_env: native-tft
platform: linux/arm/v7
docker-deb-amd64:
uses: ./.github/workflows/docker_build.yml
with:
distro: ${{ matrix.distro }}
platform: ${{ matrix.platform }}
runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
pio_env: ${{ matrix.pio_env }}
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:
@@ -139,7 +260,18 @@ jobs:
- rp2350
- stm32
runs-on: ubuntu-latest
needs: [version, build]
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

View File

@@ -41,7 +41,7 @@ jobs:
# step 4
- name: publish code scanning alerts
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: report.sarif
category: semgrep

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: Stale PR+Issues
uses: actions/stale@v10.1.0
uses: actions/stale@v10.0.0
with:
days-before-stale: 45
exempt-issue-labels: pinned,3.0

View File

@@ -40,7 +40,7 @@ jobs:
- name: Integration test
run: |
.pio/build/coverage/program -s &
.pio/build/coverage/program &
PID=$!
timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
echo "Simulator started, launching python test..."

View File

@@ -4,20 +4,20 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.7.3
ref: v1.7.2
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.477
- renovate@41.144.1
- checkov@3.2.471
- renovate@41.127.2
- prettier@3.6.2
- trufflehog@3.90.8
- yamllint@1.37.1
- bandit@1.8.6
- trivy@0.67.1
- trivy@0.66.0
- taplo@0.10.0
- ruff@0.14.0
- isort@6.1.0
- ruff@0.13.1
- isort@6.0.1
- markdownlint@0.45.0
- oxipng@9.1.5
- svgo@4.0.0

View File

@@ -3,7 +3,7 @@
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
FROM python:3.14-slim-trixie AS builder
FROM python:3.13-slim-trixie AS builder
ARG PIO_ENV=native
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC

View File

@@ -3,7 +3,7 @@
# trunk-ignore-all(hadolint/DL3018): Do not pin apk package versions
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
FROM python:3.14-alpine3.22 AS builder
FROM python:3.13-alpine3.22 AS builder
ARG PIO_ENV=native
ENV PIP_ROOT_USER_ACTION=ignore

View File

@@ -31,13 +31,11 @@ build_flags =
-DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL
-DAXP_DEBUG_PORT=Serial
-DCONFIG_BT_NIMBLE_ENABLED
-DCONFIG_BT_NIMBLE_MAX_BONDS=6 # default is 3
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
-DCONFIG_BT_NIMBLE_MAX_CCCDS=20
-DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
-DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
-DSERIAL_BUFFER_SIZE=4096
-DSERIAL_HAS_ON_RECEIVE
-DLIBPAX_ARDUINO
-DLIBPAX_WIFI
-DLIBPAX_BLE
@@ -57,7 +55,7 @@ lib_deps =
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
https://github.com/lewisxhe/XPowersLib/archive/v0.3.1.zip
https://github.com/lewisxhe/XPowersLib/archive/v0.3.0.zip
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

View File

@@ -28,7 +28,7 @@ lib_deps =
${environmental_extra.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.1
lewisxhe/XPowersLib@0.3.0
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

View File

@@ -1,32 +1,28 @@
#!/usr/bin/env python3
#!/usr/bin/env python
"""Generate the CI matrix."""
import argparse
import json
import sys
import random
import re
from platformio.project.config import ProjectConfig
parser = argparse.ArgumentParser(description="Generate the CI matrix")
parser.add_argument("platform", help="Platform to build for")
parser.add_argument(
"--level",
choices=["extra", "pr"],
nargs="*",
default=[],
help="Board level to build for (omit for full release boards)",
)
args = parser.parse_args()
options = sys.argv[1:]
outlist = []
if len(options) < 1:
print(json.dumps(outlist))
exit(1)
cfg = ProjectConfig.get_instance()
pio_envs = cfg.envs()
# Gather all PlatformIO environments for filtering later
all_envs = []
for pio_env in pio_envs:
env_build_flags = cfg.get(f"env:{pio_env}", "build_flags")
env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags')
env_platform = None
for flag in env_build_flags:
# Extract the platform from the build flags
@@ -41,35 +37,36 @@ for pio_env in pio_envs:
exit(1)
# Store env details as a dictionary, and add to 'all_envs' list
env = {
"ci": {"board": pio_env, "platform": env_platform},
"board_level": cfg.get(f"env:{pio_env}", "board_level", default=None),
"board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)),
'name': pio_env,
'platform': env_platform,
'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None),
'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False))
}
all_envs.append(env)
# Filter outputs based on options
# Check is mutually exclusive with other options (except 'pr')
if "check" in args.platform:
if "check" in options:
for env in all_envs:
if env["board_check"]:
if "pr" in args.level:
if env["board_level"] == "pr":
outlist.append(env["ci"])
if env['board_check']:
if "pr" in options:
if env['board_level'] == 'pr':
outlist.append(env['name'])
else:
outlist.append(env["ci"])
outlist.append(env['name'])
# Filter (non-check) builds by platform
else:
for env in all_envs:
if args.platform == env["ci"]["platform"] or args.platform == "all":
if options[0] == env['platform']:
# Always include board_level = 'pr'
if env["board_level"] == "pr":
outlist.append(env["ci"])
if env['board_level'] == 'pr':
outlist.append(env['name'])
# Include board_level = 'extra' when requested
elif "extra" in args.level and env["board_level"] == "extra":
outlist.append(env["ci"])
elif "extra" in options and env['board_level'] == "extra":
outlist.append(env['name'])
# If no board level is specified, include in release builds (not PR)
elif "pr" not in args.level and not env["board_level"]:
outlist.append(env["ci"])
elif "pr" not in options and not env['board_level']:
outlist.append(env['name'])
# Return as a JSON list
print(json.dumps(outlist))

View File

@@ -1,116 +0,0 @@
#!/bin/bash
# Script to cancel all running GitHub Actions workflows
# Requires GitHub CLI (gh) to be installed and authenticated
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if gh CLI is installed
if ! command -v gh &> /dev/null; then
print_error "GitHub CLI (gh) is not installed. Please install it first:"
echo " brew install gh"
echo " Or visit: https://cli.github.com/"
exit 1
fi
# Check if authenticated
if ! gh auth status &> /dev/null; then
print_error "GitHub CLI is not authenticated. Please run:"
echo " gh auth login"
exit 1
fi
# Get repository info
REPO=$(gh repo view --json owner,name -q '.owner.login + "/" + .name')
if [[ -z "$REPO" ]]; then
print_error "Could not determine repository. Make sure you're in a GitHub repository."
exit 1
fi
print_status "Working with repository: $REPO"
# Get all active workflows (both queued and in-progress)
print_status "Fetching active workflows (queued and in-progress)..."
QUEUED_WORKFLOWS=$(gh run list --status queued --json databaseId,displayTitle,headBranch,status --limit 100)
IN_PROGRESS_WORKFLOWS=$(gh run list --status in_progress --json databaseId,displayTitle,headBranch,status --limit 100)
# Combine both lists
ALL_WORKFLOWS=$(echo "$QUEUED_WORKFLOWS $IN_PROGRESS_WORKFLOWS" | jq -s 'add | unique_by(.databaseId)')
if [[ "$ALL_WORKFLOWS" == "[]" ]]; then
print_status "No active workflows found."
exit 0
fi
# Parse and display active workflows
echo
print_warning "Found active workflows:"
echo "$ALL_WORKFLOWS" | jq -r '.[] | " - \(.displayTitle) (Branch: \(.headBranch), Status: \(.status), ID: \(.databaseId))"'
echo
read -p "Do you want to cancel ALL these workflows? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_status "Cancelled by user."
exit 0
fi
# Cancel each workflow
print_status "Cancelling workflows..."
CANCELLED_COUNT=0
FAILED_COUNT=0
while IFS= read -r WORKFLOW_ID; do
if [[ -n "$WORKFLOW_ID" ]]; then
print_status "Cancelling workflow ID: $WORKFLOW_ID"
if gh run cancel "$WORKFLOW_ID" 2>/dev/null; then
((CANCELLED_COUNT++))
else
print_error "Failed to cancel workflow ID: $WORKFLOW_ID"
((FAILED_COUNT++))
fi
fi
done < <(echo "$ALL_WORKFLOWS" | jq -r '.[].databaseId')
echo
print_status "Summary:"
echo " - Cancelled: $CANCELLED_COUNT workflows"
if [[ $FAILED_COUNT -gt 0 ]]; then
echo " - Failed: $FAILED_COUNT workflows"
fi
print_status "Done!"
# Optional: Show remaining active workflows
echo
print_status "Checking for any remaining active workflows..."
REMAINING_QUEUED=$(gh run list --status queued --json databaseId --limit 10)
REMAINING_IN_PROGRESS=$(gh run list --status in_progress --json databaseId --limit 10)
REMAINING_ALL=$(echo "$REMAINING_QUEUED $REMAINING_IN_PROGRESS" | jq -s 'add | unique_by(.databaseId)')
if [[ "$REMAINING_ALL" == "[]" ]]; then
print_status "All workflows successfully cancelled."
else
REMAINING_COUNT=$(echo "$REMAINING_ALL" | jq '. | length')
print_warning "Still $REMAINING_COUNT workflows active (may take a moment to update status)"
fi

View File

@@ -87,12 +87,6 @@
</screenshots>
<releases>
<release version="2.7.13" date="2025-10-11">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13</url>
</release>
<release version="2.7.12" date="2025-10-01">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.12</url>
</release>
<release version="2.7.11" date="2025-09-24">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.11</url>
</release>

View File

@@ -86,7 +86,7 @@ if platform.name == "espressif32":
if platform.name == "nordicnrf52":
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex",
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py \"$BUILD_DIR/firmware.hex\" -c -f 0xADA52840 -o \"$BUILD_DIR/firmware.uf2\"",
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2",
"Generating UF2 file"))
Import("projenv")

View File

@@ -1 +1 @@
2.6.6
2.6.4

View File

@@ -1,37 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-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",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "heltec_wireless_tracker_v2"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "Heltec Wireless Tracker V2",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://heltec.org",
"vendor": "Heltec"
}

View File

@@ -1,52 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "Muzi R1 Neo",
"mcu": "nrf52840",
"variant": "r1-neo",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino", "freertos"],
"name": "WisCore RAK4631 Board",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://muzi.works/",
"vendor": "Muzi Works"
}

13
debian/changelog vendored
View File

@@ -1,10 +1,4 @@
meshtasticd (2.7.13.0) unstable; urgency=medium
* Version 2.7.13
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Sat, 11 Oct 2025 15:27:28 +0000
meshtasticd (2.7.12.0) unstable; urgency=medium
meshtasticd (2.7.11.0) UNRELEASED; urgency=medium
[ Austin Lane ]
* Initial packaging
@@ -13,7 +7,4 @@ meshtasticd (2.7.12.0) unstable; urgency=medium
[ ]
* GitHub Actions Automatic version bump
[ GitHub Actions ]
* Version 2.7.12
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 01 Oct 2025 19:51:41 +0000
-- <github-actions[bot]@users.noreply.github.com> Wed, 24 Sep 2025 11:01:13 +0000

View File

@@ -56,7 +56,6 @@ build_flags = -Wno-missing-field-initializers
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
#-D OLED_PL=1
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
#-D DEBUG_LOOP_TIMING=1 ; uncomment to add main loop timing logs
monitor_speed = 115200
monitor_filters = direct
@@ -70,7 +69,7 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip
# renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master
https://github.com/meshtastic/ArduinoThread/archive/b841b0415721f1341ea41cccfb4adccfaf951567.zip
https://github.com/meshtastic/ArduinoThread/archive/7c3ee9e1951551b949763b1f5280f8db1fa4068d.zip
# renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb
nanopb/Nanopb@0.4.91
# renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32
@@ -120,7 +119,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/19b7855e9a1d9deff37391659ca7194e4ef57c43.zip
https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]

View File

@@ -11,11 +11,6 @@
#include <AudioOutputI2S.h>
#include <ESP8266SAM.h>
#ifdef USE_XL9555
#include "ExtensionIOXL9555.hpp"
extern ExtensionIOXL9555 io;
#endif
#define AUDIO_THREAD_INTERVAL_MS 100
class AudioThread : public concurrency::OSThread
@@ -25,16 +20,12 @@ class AudioThread : public concurrency::OSThread
void beginRttl(const void *data, uint32_t len)
{
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
#endif
setCPUFast(true);
rtttlFile = new AudioFileSourcePROGMEM(data, len);
i2sRtttl = new AudioGeneratorRTTTL();
i2sRtttl->begin(rtttlFile, audioOut);
}
// Also handles actually playing the RTTTL, needs to be called in loop
bool isPlaying()
{
if (i2sRtttl != nullptr) {
@@ -54,9 +45,6 @@ class AudioThread : public concurrency::OSThread
rtttlFile = nullptr;
setCPUFast(false);
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, LOW);
#endif
}
void readAloud(const char *text)
@@ -67,16 +55,10 @@ class AudioThread : public concurrency::OSThread
i2sRtttl = nullptr;
}
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
#endif
ESP8266SAM *sam = new ESP8266SAM;
sam->Say(audioOut, text);
delete sam;
setCPUFast(false);
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, LOW);
#endif
}
protected:

View File

@@ -76,6 +76,9 @@ const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role
case meshtastic_Config_DeviceConfig_Role_ROUTER_LATE:
return "Router Late";
break;
case meshtastic_Config_DeviceConfig_Role_REPEATER:
return "Repeater";
break;
default:
return "Unknown";
break;

View File

@@ -562,7 +562,6 @@ class AnalogBatteryLevel : public HasBatteryLevel
config.power.device_battery_ina_address) {
if (!ina226Sensor.isInitialized())
return ina226Sensor.runOnce() > 0;
return ina226Sensor.isRunning();
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
config.power.device_battery_ina_address) {
if (!ina260Sensor.isInitialized())
@@ -692,16 +691,6 @@ bool Power::setup()
#ifdef NRF_APM
found = true;
#endif
#ifdef EXT_PWR_DETECT
attachInterrupt(
EXT_PWR_DETECT,
[]() {
power->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
},
CHANGE);
#endif
enabled = found;
low_voltage_counter = 0;

View File

@@ -6,14 +6,6 @@
#include "configuration.h"
#include "time.h"
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
#define IS_USB_SERIAL
#ifdef SERIAL_HAS_ON_RECEIVE
#undef SERIAL_HAS_ON_RECEIVE
#endif
#include "HWCDC.h"
#endif
#ifdef RP2040_SLOW_CLOCK
#define Port Serial2
#else
@@ -30,12 +22,7 @@ SerialConsole *console;
void consoleInit()
{
auto sc = new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
#if defined(SERIAL_HAS_ON_RECEIVE)
// onReceive does only exist for HardwareSerial not for USB CDC serial
Port.onReceive([sc]() { sc->rxInt(); });
#endif
new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
DEBUG_PORT.rpInit(); // Simply sets up semaphore
}
@@ -79,20 +66,13 @@ int32_t SerialConsole::runOnce()
{
#ifdef HELTEC_MESH_SOLAR
//After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
if (moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port &&
moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) {
if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port
&& moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)
{
return 250;
}
#endif
int32_t delay = runOncePart();
#if defined(SERIAL_HAS_ON_RECEIVE) || defined(CONFIG_IDF_TARGET_ESP32S2)
return Port.available() ? delay : INT32_MAX;
#elif defined(IS_USB_SERIAL)
return HWCDC::isPlugged() ? delay : (1000 * 20);
#else
return delay;
#endif
return runOncePart();
}
void SerialConsole::flush()
@@ -100,18 +80,6 @@ void SerialConsole::flush()
Port.flush();
}
// trigger tx of serial data
void SerialConsole::onNowHasData(uint32_t fromRadioNum)
{
setIntervalFromNow(0);
}
// trigger rx of serial data
void SerialConsole::rxInt()
{
setIntervalFromNow(0);
}
// For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages
bool SerialConsole::checkIsConnected()
{

View File

@@ -32,14 +32,11 @@ class SerialConsole : public StreamAPI, public RedirectablePrint, private concur
virtual int32_t runOnce() override;
void flush();
void rxInt();
protected:
/// Check the current underlying physical link to see if the client is currently connected
virtual bool checkIsConnected() override;
virtual void onNowHasData(uint32_t fromRadioNum) override;
/// Possibly switch to protobufs if we see a valid protobuf message
virtual void log_to_serial(const char *logLevel, const char *format, va_list arg);
};

View File

@@ -5,7 +5,7 @@
BuzzerFeedbackThread *buzzerFeedbackThread;
BuzzerFeedbackThread::BuzzerFeedbackThread()
BuzzerFeedbackThread::BuzzerFeedbackThread() : OSThread("BuzzerFeedback")
{
if (inputBroker)
inputObserver.observe(inputBroker);
@@ -15,11 +15,14 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
{
// Only provide feedback if buzzer is enabled for notifications
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY ||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY) {
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY) {
return 0; // Let other handlers process the event
}
// Track last event time for potential future use
lastEventTime = millis();
needsUpdate = true;
// Handle different input events with appropriate buzzer feedback
switch (event->inputEvent) {
case INPUT_BROKER_USER_PRESS:
@@ -59,3 +62,14 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
return 0; // Allow other handlers to process the event
}
int32_t BuzzerFeedbackThread::runOnce()
{
// This thread is primarily event-driven, but we can use runOnce
// for any periodic tasks if needed in the future
needsUpdate = false;
// Run every 100ms when active, less frequently when idle
return needsUpdate ? 100 : 1000;
}

View File

@@ -4,7 +4,7 @@
#include "concurrency/OSThread.h"
#include "input/InputBroker.h"
class BuzzerFeedbackThread
class BuzzerFeedbackThread : public concurrency::OSThread
{
CallbackObserver<BuzzerFeedbackThread, const InputEvent *> inputObserver =
CallbackObserver<BuzzerFeedbackThread, const InputEvent *>(this, &BuzzerFeedbackThread::handleInputEvent);
@@ -12,6 +12,13 @@ class BuzzerFeedbackThread
public:
BuzzerFeedbackThread();
int handleInputEvent(const InputEvent *event);
protected:
virtual int32_t runOnce() override;
private:
uint32_t lastEventTime = 0;
bool needsUpdate = false;
};
extern BuzzerFeedbackThread *buzzerFeedbackThread;

View File

@@ -90,9 +90,7 @@ void OSThread::run()
if (heap < newHeap)
LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
#endif
#ifdef DEBUG_LOOP_TIMING
LOG_DEBUG("====== Thread next run in: %d", newDelay);
#endif
runned();
if (newDelay >= 0)

View File

@@ -33,9 +33,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "pcf8563.h"
#endif
/* Offer chance for variant-specific defines */
#include "variant.h"
// -----------------------------------------------------------------------------
// Version
// -----------------------------------------------------------------------------
@@ -120,12 +117,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define SX126X_MAX_POWER 22
#endif
#ifdef USE_GC1109_PA
// Power Amps are often non-linear, so we can use an array of values for the power curve
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
#endif
// Default system gain to 0 if not defined
#ifndef TX_GAIN_LORA
#define TX_GAIN_LORA 0
@@ -263,6 +254,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// convert 24-bit color to 16-bit (56K)
#define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
/* Step #1: offer chance for variant-specific defines */
#include "variant.h"
#if defined(VEXT_ENABLE) && !defined(VEXT_ON_VALUE)
// Older variant.h files might not be defining this value, so stay with the old default
#define VEXT_ON_VALUE LOW

View File

@@ -25,8 +25,8 @@ ScanI2C::FoundDevice ScanI2C::firstScreen() const
ScanI2C::FoundDevice ScanI2C::firstRTC() const
{
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563, RTC_RX8130CE};
return firstOfOrNONE(3, types);
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563};
return firstOfOrNONE(2, types);
}
ScanI2C::FoundDevice ScanI2C::firstKeyboard() const

View File

@@ -14,7 +14,6 @@ class ScanI2C
SCREEN_ST7567,
RTC_RV3028,
RTC_PCF8563,
RTC_RX8130CE,
CARDKB,
TDECKKB,
BBQ10KB,

View File

@@ -1,16 +0,0 @@
#include "ScanI2CConsumer.h"
#include <forward_list>
static std::forward_list<ScanI2CConsumer *> ScanI2CConsumers;
ScanI2CConsumer::ScanI2CConsumer()
{
ScanI2CConsumers.push_front(this);
}
void ScanI2CCompleted(ScanI2C *i2cScanner)
{
for (ScanI2CConsumer *consumer : ScanI2CConsumers) {
consumer->i2cScanFinished(i2cScanner);
}
}

View File

@@ -1,13 +0,0 @@
#pragma once
#include "ScanI2C.h"
#include <stddef.h>
class ScanI2CConsumer
{
public:
ScanI2CConsumer();
virtual void i2cScanFinished(ScanI2C *i2cScanner) = 0;
};
void ScanI2CCompleted(ScanI2C *i2cScanner);

View File

@@ -197,9 +197,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#ifdef PCF8563_RTC
SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563", (uint8_t)addr.address)
#endif
#ifdef RX8130CE_RTC
SCAN_SIMPLE_CASE(RX8130CE_RTC, RTC_RX8130CE, "RX8130CE", (uint8_t)addr.address)
#endif
case CARDKB_ADDR:
// Do we have the RAK14006 instead?
@@ -378,8 +375,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c ||
registerValue == 0xc8d) {
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c || registerValue == 0xc8d) {
type = SHT4X;
logFoundDevice("SHT4X", (uint8_t)addr.address);
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
@@ -581,7 +577,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
scanPort(port, nullptr, 0);
}
TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address)
TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const
{
if (address.port == ScanI2C::I2CPort::WIRE) {
return &Wire;

View File

@@ -23,12 +23,12 @@ class ScanI2CTwoWire : public ScanI2C
ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override;
TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const;
bool exists(ScanI2C::DeviceType) const override;
size_t countDevices() const override;
static TwoWire *fetchI2CBus(ScanI2C::DeviceAddress);
protected:
FoundDevice firstOfOrNONE(size_t, DeviceType[]) const override;

View File

@@ -494,10 +494,22 @@ bool GPS::setup()
if (!didSerialInit) {
int msglen = 0;
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
#ifdef TRACKER_T1000_E
// add power up/down strategy, improve ag3335 detection success
digitalWrite(PIN_GPS_EN, LOW);
delay(500);
digitalWrite(GPS_VRTC_EN, LOW);
delay(1000);
digitalWrite(GPS_VRTC_EN, HIGH);
delay(500);
digitalWrite(PIN_GPS_EN, HIGH);
delay(1000);
#endif
if (probeTries < GPS_PROBETRIES) {
LOG_DEBUG("Probe for GPS at %d", serialSpeeds[speedSelect]);
gnssModel = probe(serialSpeeds[speedSelect]);
if (gnssModel == GNSS_MODEL_UNKNOWN) {
if (currentStep == 0 && ++speedSelect == array_count(serialSpeeds)) {
if (++speedSelect == array_count(serialSpeeds)) {
speedSelect = 0;
++probeTries;
}
@@ -506,9 +518,10 @@ 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]);
if (gnssModel == GNSS_MODEL_UNKNOWN) {
if (currentStep == 0 && ++speedSelect == array_count(rareSerialSpeeds)) {
if (++speedSelect == array_count(rareSerialSpeeds)) {
LOG_WARN("Give up on GPS probe and set to %d", GPS_BAUDRATE);
return true;
}
@@ -1020,7 +1033,7 @@ void GPS::down()
LOG_DEBUG("%us until next search", sleepTime / 1000);
// If update interval less than 10 seconds, no attempt to sleep
if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS || sleepTime == 0)
if (updateInterval <= 10 * 1000UL || sleepTime == 0)
setPowerState(GPS_IDLE);
else {
@@ -1081,7 +1094,7 @@ int32_t GPS::runOnce()
return disable();
}
if (!setup())
return currentDelay; // Setup failed, re-run in two seconds
return 2000; // Setup failed, re-run in two seconds
// We have now loaded our saved preferences from flash
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
@@ -1091,29 +1104,11 @@ int32_t GPS::runOnce()
publishUpdate();
}
// ======================== GPS_ACTIVE state ========================
// In GPS_ACTIVE state, GPS is powered on and we're receiving NMEA messages.
// We use the following logic to determine when to update the local position
// or time by running GPS::publishUpdate.
// Note: Local position update is asynchronous to position broadcast. We
// generally run this state every gps_update_interval seconds, and in most cases
// gps_update_interval is faster than the position broadcast interval so there's a
// fresh position ready when the device wants to broadcast one on the mesh.
//
// 1. Got a time for the first time --> set the time, don't publish.
// 2. Got a lock for the first time
// --> If gps_update_interval is <= 10s --> publishUpdate
// --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
// 3. Got a lock after turning back on
// --> If gps_update_interval is <= 10s --> publishUpdate
// --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
// 4. Hold has expired
// --> If we have a time and a location --> publishUpdate
// --> down()
// 5. Search time has expired
// --> If we have a time and a location --> publishUpdate
// --> If we had a location before but don't now --> publishUpdate
// --> down()
// Repeaters have no need for GPS
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
return disable();
}
if (whileActive()) {
// if we have received valid NMEA claim we are connected
setConnected();
@@ -1123,81 +1118,55 @@ int32_t GPS::runOnce()
if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue())
up();
// quality of the previous fix. We set it to 0 when we go down, so it's a way
// to check if we're getting a lock after being GPS_OFF.
uint8_t prev_fixQual = fixQual;
if (powerState == GPS_ACTIVE) {
// if gps_update_interval is <=10s, GPS never goes off, so we treat that differently
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
// 1. Got a time for the first time
// If we've already set time from the GPS, no need to ask the GPS
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
gotTime = true;
shouldPublish = true;
}
// 2. Got a lock for the first time, or 3. Got a lock after turning back on
uint8_t prev_fixQual = fixQual;
bool gotLoc = lookForLocation();
if (gotLoc) {
#ifdef GPS_DEBUG
if (!hasValidLocation) { // declare that we have location ASAP
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
LOG_DEBUG("hasValidLocation RISING EDGE");
}
#endif
if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS) {
hasValidLocation = true;
shouldPublish = true;
} else if (!hasValidLocation || prev_fixQual == 0 || (fixHoldEnds + GPS_THREAD_INTERVAL) < millis()) {
hasValidLocation = true;
// Hold for up to 20secs after getting a lock to download ephemeris etc
uint32_t holdTime = updateInterval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS;
if (holdTime > GPS_FIX_HOLD_MAX_MS)
holdTime = GPS_FIX_HOLD_MAX_MS;
fixHoldEnds = millis() + holdTime;
#ifdef GPS_DEBUG
LOG_DEBUG("Holding for %ums after lock", holdTime);
#endif
// Hold for 20secs after getting a lock to download ephemeris etc
fixHoldEnds = millis() + 20000;
}
if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on.
fixHoldEnds = millis() + 20000;
shouldPublish = true; // Publish immediately, since next publish is at end of hold
}
bool tooLong = scheduling.searchedTooLong();
if (tooLong && !gotLoc) {
if (tooLong)
LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
// Once we get a location we no longer desperately want an update
if ((gotLoc && gotTime) || tooLong) {
if (tooLong && !gotLoc) {
// we didn't get a location during this ack window, therefore declare loss of lock
if (hasValidLocation) {
LOG_DEBUG("hasValidLocation FALLING EDGE");
}
p = meshtastic_Position_init_default;
hasValidLocation = false;
shouldPublish = true;
#ifdef GPS_DEBUG
LOG_DEBUG("hasValidLocation FALLING EDGE");
#endif
}
}
// Hold has expired , Search time has expired, we got a time only, or we never needed to hold.
bool holdExpired = (fixHoldEnds != 0 && millis() > fixHoldEnds);
if (shouldPublish || tooLong || holdExpired) {
if (gotTime && hasValidLocation) {
shouldPublish = true;
}
if (shouldPublish) {
fixHoldEnds = 0;
if (millis() > fixHoldEnds) {
shouldPublish = true; // publish our update at the end of the lock hold
publishUpdate();
}
// There's a chance we just got a time, so keep going to see if we can get a location too
if (tooLong || holdExpired) {
down();
}
#ifdef GPS_DEBUG
} else if (fixHoldEnds != 0) {
} else {
LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view);
#endif
}
}
// ===================== end GPS_ACTIVE state ========================
// If state has changed do a publish
publishUpdate();
if (config.position.fixed_position == true && hasValidLocation)
return disable(); // This should trigger when we have a fixed position, and get that first position
@@ -1254,10 +1223,6 @@ static const char *DETECTED_MESSAGE = "%s detected";
GnssModel_t GPS::probe(int serialSpeed)
{
uint8_t buffer[768] = {0};
switch (currentStep) {
case 0: {
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
_serial_gps->end();
_serial_gps->begin(serialSpeed);
@@ -1267,12 +1232,13 @@ GnssModel_t GPS::probe(int serialSpeed)
_serial_gps->begin(serialSpeed);
#else
if (_serial_gps->baudRate() != serialSpeed) {
LOG_DEBUG("Set GPS Baud to %i", serialSpeed);
LOG_DEBUG("Set Baud to %i", serialSpeed);
_serial_gps->updateBaudRate(serialSpeed);
}
#endif
memset(&ublox_info, 0, sizeof(ublox_info));
uint8_t buffer[768] = {0};
delay(100);
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
@@ -1287,31 +1253,19 @@ GnssModel_t GPS::probe(int serialSpeed)
_serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n");
_serial_gps->write("$CFGMSG,0,2,0,1*18\r\n");
_serial_gps->write("$CFGMSG,0,3,0,1*19\r\n");
currentDelay = 20;
currentStep = 1;
return GNSS_MODEL_UNKNOWN;
}
case 1: {
delay(20);
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
std::vector<ChipInfo> unicore = {
{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
currentDelay = 20;
currentStep = 2;
return GNSS_MODEL_UNKNOWN;
}
case 2: {
std::vector<ChipInfo> atgm = {
{"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
{"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
currentDelay = 20;
currentStep = 3;
return GNSS_MODEL_UNKNOWN;
}
case 3: {
/* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
_serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
_serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
@@ -1320,18 +1274,9 @@ GnssModel_t GPS::probe(int serialSpeed)
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
currentDelay = 20;
currentStep = 4;
return GNSS_MODEL_UNKNOWN;
}
case 4: {
PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
currentDelay = 20;
currentStep = 5;
return GNSS_MODEL_UNKNOWN;
}
case 5: {
// Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
@@ -1342,11 +1287,7 @@ GnssModel_t GPS::probe(int serialSpeed)
{"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
currentDelay = 20;
currentStep = 6;
return GNSS_MODEL_UNKNOWN;
}
case 6: {
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
UBXChecksum(cfg_rate, sizeof(cfg_rate));
clearBuffer();
@@ -1355,8 +1296,6 @@ GnssModel_t GPS::probe(int serialSpeed)
GPS_RESPONSE response = getACK(0x06, 0x08, 750);
if (response == GNSS_RESPONSE_NONE) {
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
currentDelay = 2000;
currentStep = 0;
return GNSS_MODEL_UNKNOWN;
} else if (response == GNSS_RESPONSE_FRAME_ERRORS) {
LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed);
@@ -1440,11 +1379,7 @@ GnssModel_t GPS::probe(int serialSpeed)
return GNSS_MODEL_UBLOX10;
}
}
}
}
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
currentDelay = 2000;
currentStep = 0;
return GNSS_MODEL_UNKNOWN;
}

View File

@@ -16,9 +16,6 @@
#define GPS_EN_ACTIVE 1
#endif
static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL;
static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000;
typedef enum {
GNSS_MODEL_ATGM336H,
GNSS_MODEL_MTK,
@@ -154,8 +151,6 @@ class GPS : private concurrency::OSThread
TinyGPSPlus reader;
uint8_t fixQual = 0; // fix quality from GPGGA
uint32_t lastChecksumFailCount = 0;
uint8_t currentStep = 0;
int32_t currentDelay = 2000;
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
// (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field
@@ -178,6 +173,8 @@ class GPS : private concurrency::OSThread
*/
bool hasValidLocation = false; // default to false, until we complete our first read
bool isInPowersave = false;
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
bool hasGPS = false; // Do we have a GPS we are talking to

View File

@@ -109,35 +109,6 @@ RTCSetResult readFromRTC()
}
return RTCSetResultSuccess;
}
#elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) {
uint32_t now = millis();
ArtronShop_RX8130CE rtc(&Wire);
tm t;
if (rtc.getTime(&t)) {
tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
LOG_DEBUG("Read RTC time from RX8130CE getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900,
t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
#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();
}
return RTCSetResultInvalidTime;
}
#endif
if (currentQuality == RTCQualityNone) {
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
currentQuality = RTCQualityDevice;
}
return RTCSetResultSuccess;
}
}
#else
if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis();
@@ -243,17 +214,6 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
}
#elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) {
ArtronShop_RX8130CE rtc(&Wire);
tm *t = gmtime(&tv->tv_sec);
if (rtc.setTime(*t)) {
LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1,
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
} else {
LOG_WARN("Failed to set time for RX8130CE");
}
}
#elif defined(ARCH_ESP32)
settimeofday(tv, NULL);
#endif

View File

@@ -4,10 +4,6 @@
#include "sys/time.h"
#include <Arduino.h>
#ifdef RX8130CE_RTC
#include <ArtronShop_RX8130CE.h>
#endif
enum RTCQuality {
/// We haven't had our RTC set yet

View File

@@ -1,687 +0,0 @@
/*----------------------------------------------------------------------------/
Lovyan GFX - Graphics library for embedded devices.
Original Source:
https://github.com/lovyan03/LovyanGFX/
Licence:
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
Author:
[lovyan03](https://twitter.com/lovyan03)
Contributors:
[ciniml](https://github.com/ciniml)
[mongonta0716](https://github.com/mongonta0716)
[tobozo](https://github.com/tobozo)
Porting for SDL:
[imliubo](https://github.com/imliubo)
/----------------------------------------------------------------------------*/
#include "Panel_sdl.hpp"
#if defined(SDL_h_)
// #include "../common.hpp"
// #include "../../misc/common_function.hpp"
// #include "../../Bus.hpp"
#include <list>
#include <math.h>
#include <vector>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace lgfx
{
inline namespace v1
{
SDL_Keymod Panel_sdl::_keymod = KMOD_NONE;
static SDL_semaphore *_update_in_semaphore = nullptr;
static SDL_semaphore *_update_out_semaphore = nullptr;
volatile static uint32_t _in_step_exec = 0;
volatile static uint32_t _msec_step_exec = 512;
static bool _inited = false;
static bool _all_close = false;
volatile uint8_t Panel_sdl::_gpio_dummy_values[EMULATED_GPIO_MAX];
static inline void *heap_alloc_dma(size_t length)
{
return malloc(length);
} // aligned_alloc(16, length);
static inline void heap_free(void *buf)
{
free(buf);
}
static std::list<monitor_t *> _list_monitor;
static monitor_t *const getMonitorByWindowID(uint32_t windowID)
{
for (auto &m : _list_monitor) {
if (SDL_GetWindowID(m->window) == windowID) {
return m;
}
}
return nullptr;
}
//----------------------------------------------------------------------------
static std::vector<Panel_sdl::KeyCodeMapping_t> _key_code_map;
void Panel_sdl::addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio)
{
if (gpio > EMULATED_GPIO_MAX)
return;
KeyCodeMapping_t map;
map.keycode = keyCode;
map.gpio = gpio;
_key_code_map.push_back(map);
}
int Panel_sdl::getKeyCodeMapping(SDL_KeyCode keyCode)
{
for (const auto &i : _key_code_map) {
if (i.keycode == keyCode)
return i.gpio;
}
return -1;
}
void Panel_sdl::_event_proc(void)
{
SDL_Event event;
while (SDL_PollEvent(&event)) {
if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) {
auto mon = getMonitorByWindowID(event.button.windowID);
int gpio = -1;
/// Check key mapping
gpio = getKeyCodeMapping((SDL_KeyCode)event.key.keysym.sym);
if (gpio < 0) {
switch (event.key.keysym.sym) { /// M5StackのBtnABtnCのエミュレート;
// case SDLK_LEFT: gpio = 39; break;
// case SDLK_DOWN: gpio = 38; break;
// case SDLK_RIGHT: gpio = 37; break;
// case SDLK_UP: gpio = 36; break;
/// L/Rキーで画面回転
case SDLK_r:
case SDLK_l:
if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) {
if (mon != nullptr) {
mon->frame_rotation = (mon->frame_rotation += event.key.keysym.sym == SDLK_r ? 1 : -1);
int x, y, w, h;
SDL_GetWindowSize(mon->window, &w, &h);
SDL_GetWindowPosition(mon->window, &x, &y);
SDL_SetWindowSize(mon->window, h, w);
SDL_SetWindowPosition(mon->window, x + (w - h) / 2, y + (h - w) / 2);
mon->panel->sdl_invalidate();
}
}
break;
/// 16キーで画面拡大率変更
case SDLK_1:
case SDLK_2:
case SDLK_3:
case SDLK_4:
case SDLK_5:
case SDLK_6:
if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) {
if (mon != nullptr) {
int size = 1 + (event.key.keysym.sym - SDLK_1);
_update_scaling(mon, size, size);
}
}
break;
default:
continue;
}
}
if (event.type == SDL_KEYDOWN) {
Panel_sdl::gpio_lo(gpio);
} else {
Panel_sdl::gpio_hi(gpio);
}
} else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION) {
auto mon = getMonitorByWindowID(event.button.windowID);
if (mon != nullptr) {
{
int x, y, w, h;
SDL_GetWindowSize(mon->window, &w, &h);
SDL_GetMouseState(&x, &y);
float sf = sinf(mon->frame_angle * M_PI / 180);
float cf = cosf(mon->frame_angle * M_PI / 180);
x -= w / 2.0f;
y -= h / 2.0f;
float nx = y * sf + x * cf;
float ny = y * cf - x * sf;
if (mon->frame_rotation & 1) {
std::swap(w, h);
}
x = (nx * mon->frame_width / w) + (mon->frame_width >> 1);
y = (ny * mon->frame_height / h) + (mon->frame_height >> 1);
mon->touch_x = x - mon->frame_inner_x;
mon->touch_y = y - mon->frame_inner_y;
}
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) {
mon->touched = true;
}
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) {
mon->touched = false;
}
}
} else if (event.type == SDL_WINDOWEVENT) {
auto monitor = getMonitorByWindowID(event.window.windowID);
if (monitor) {
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
int mw, mh;
SDL_GetRendererOutputSize(monitor->renderer, &mw, &mh);
if (monitor->frame_rotation & 1) {
std::swap(mw, mh);
}
monitor->scaling_x = (mw * 2 / monitor->frame_width) / 2.0f;
monitor->scaling_y = (mh * 2 / monitor->frame_height) / 2.0f;
monitor->panel->sdl_invalidate();
} else if (event.window.event == SDL_WINDOWEVENT_CLOSE) {
monitor->closing = true;
}
}
} else if (event.type == SDL_QUIT) {
for (auto &m : _list_monitor) {
m->closing = true;
}
}
}
}
/// デバッガでステップ実行されていることを検出するスレッド用関数。
static int detectDebugger(bool *running)
{
uint32_t prev_ms = SDL_GetTicks();
do {
SDL_Delay(1);
uint32_t ms = SDL_GetTicks();
/// 時間間隔が広すぎる場合はステップ実行中 (ブレークポイントで止まった)と判断する。
/// また、解除されたと判断した後も1023msecほど状態を維持する。
if (ms - prev_ms > 64) {
_in_step_exec = _msec_step_exec;
} else if (_in_step_exec) {
--_in_step_exec;
}
prev_ms = ms;
} while (*running);
return 0;
}
void Panel_sdl::_update_proc(void)
{
for (auto it = _list_monitor.begin(); it != _list_monitor.end();) {
if ((*it)->closing) {
if ((*it)->texture_frameimage) {
SDL_DestroyTexture((*it)->texture_frameimage);
}
SDL_DestroyTexture((*it)->texture);
SDL_DestroyRenderer((*it)->renderer);
SDL_DestroyWindow((*it)->window);
_list_monitor.erase(it++);
if (_list_monitor.empty()) {
_all_close = true;
return;
}
continue;
}
(*it)->panel->sdl_update();
++it;
}
}
int Panel_sdl::setup(void)
{
if (_inited)
return 1;
_inited = true;
/// Add default keycode mapping
/// M5StackのBtnABtnCのエミュレート;
addKeyCodeMapping(SDLK_LEFT, 39);
addKeyCodeMapping(SDLK_DOWN, 38);
addKeyCodeMapping(SDLK_RIGHT, 37);
addKeyCodeMapping(SDLK_UP, 36);
SDL_CreateThread((SDL_ThreadFunction)detectDebugger, "dbg", &_inited);
_update_in_semaphore = SDL_CreateSemaphore(0);
_update_out_semaphore = SDL_CreateSemaphore(0);
for (size_t pin = 0; pin < EMULATED_GPIO_MAX; ++pin) {
gpio_hi(pin);
}
/*Initialize the SDL*/
SDL_Init(SDL_INIT_VIDEO);
SDL_StartTextInput();
// SDL_SetThreadPriority(SDL_ThreadPriority::SDL_THREAD_PRIORITY_HIGH);
return 0;
}
int Panel_sdl::loop(void)
{
if (!_inited)
return 1;
_event_proc();
SDL_SemWaitTimeout(_update_in_semaphore, 1);
_update_proc();
_event_proc();
if (SDL_SemValue(_update_out_semaphore) == 0) {
SDL_SemPost(_update_out_semaphore);
}
return _all_close;
}
int Panel_sdl::close(void)
{
if (!_inited)
return 1;
_inited = false;
SDL_StopTextInput();
SDL_DestroySemaphore(_update_in_semaphore);
SDL_DestroySemaphore(_update_out_semaphore);
SDL_Quit();
return 0;
}
int Panel_sdl::main(int (*fn)(bool *), uint32_t msec_step_exec)
{
_msec_step_exec = msec_step_exec;
/// SDLの準備
if (0 != Panel_sdl::setup()) {
return 1;
}
/// ユーザコード関数の動作・停止フラグ
bool running = true;
/// ユーザコード関数を起動する
auto thread = SDL_CreateThread((SDL_ThreadFunction)fn, "fn", &running);
/// 全部のウィンドウが閉じられるまでSDLのイベント・描画処理を継続
while (0 == Panel_sdl::loop()) {
};
/// ユーザコード関数を終了する
running = false;
SDL_WaitThread(thread, nullptr);
/// SDLを終了する
return Panel_sdl::close();
}
void Panel_sdl::setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y)
{
monitor.scaling_x = scaling_x;
monitor.scaling_y = scaling_y;
}
void Panel_sdl::setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y)
{
monitor.frame_image = frame_image;
monitor.frame_width = frame_width;
monitor.frame_height = frame_height;
monitor.frame_inner_x = inner_x;
monitor.frame_inner_y = inner_y;
}
void Panel_sdl::setFrameRotation(uint_fast16_t frame_rotation)
{
monitor.frame_rotation = frame_rotation;
monitor.frame_angle = (monitor.frame_rotation) * 90;
}
Panel_sdl::~Panel_sdl(void)
{
_list_monitor.remove(&monitor);
SDL_DestroyMutex(_sdl_mutex);
}
Panel_sdl::Panel_sdl(void) : Panel_FrameBufferBase()
{
_sdl_mutex = SDL_CreateMutex();
_auto_display = true;
monitor.panel = this;
}
bool Panel_sdl::init(bool use_reset)
{
initFrameBuffer(_cfg.panel_width * 4, _cfg.panel_height);
bool res = Panel_FrameBufferBase::init(use_reset);
_list_monitor.push_back(&monitor);
return res;
}
color_depth_t Panel_sdl::setColorDepth(color_depth_t depth)
{
auto bits = depth & color_depth_t::bit_mask;
if (bits >= 16) {
depth = (bits > 16) ? rgb888_3Byte : rgb565_2Byte;
} else {
depth = (depth == color_depth_t::grayscale_8bit) ? grayscale_8bit : rgb332_1Byte;
}
_write_depth = depth;
_read_depth = depth;
return depth;
}
Panel_sdl::lock_t::lock_t(Panel_sdl *parent) : _parent{parent}
{
SDL_LockMutex(parent->_sdl_mutex);
};
Panel_sdl::lock_t::~lock_t(void)
{
++_parent->_modified_counter;
SDL_UnlockMutex(_parent->_sdl_mutex);
if (SDL_SemValue(_update_in_semaphore) < 2) {
SDL_SemPost(_update_in_semaphore);
if (!_in_step_exec) {
SDL_SemWaitTimeout(_update_out_semaphore, 1);
}
}
};
void Panel_sdl::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor)
{
lock_t lock(this);
Panel_FrameBufferBase::drawPixelPreclipped(x, y, rawcolor);
}
void Panel_sdl::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor)
{
lock_t lock(this);
Panel_FrameBufferBase::writeFillRectPreclipped(x, y, w, h, rawcolor);
}
void Panel_sdl::writeBlock(uint32_t rawcolor, uint32_t length)
{
// lock_t lock(this);
Panel_FrameBufferBase::writeBlock(rawcolor, length);
}
void Panel_sdl::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param, bool use_dma)
{
lock_t lock(this);
Panel_FrameBufferBase::writeImage(x, y, w, h, param, use_dma);
}
void Panel_sdl::writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param)
{
lock_t lock(this);
Panel_FrameBufferBase::writeImageARGB(x, y, w, h, param);
}
void Panel_sdl::writePixels(pixelcopy_t *param, uint32_t len, bool use_dma)
{
lock_t lock(this);
Panel_FrameBufferBase::writePixels(param, len, use_dma);
}
void Panel_sdl::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h)
{
(void)x;
(void)y;
(void)w;
(void)h;
if (_in_step_exec) {
if (_display_counter != _modified_counter) {
do {
SDL_SemPost(_update_in_semaphore);
SDL_SemWaitTimeout(_update_out_semaphore, 1);
} while (_display_counter != _modified_counter);
SDL_Delay(1);
}
}
}
uint_fast8_t Panel_sdl::getTouchRaw(touch_point_t *tp, uint_fast8_t count)
{
(void)count;
tp->x = monitor.touch_x;
tp->y = monitor.touch_y;
tp->size = monitor.touched ? 1 : 0;
tp->id = 0;
return monitor.touched;
}
void Panel_sdl::setWindowTitle(const char *title)
{
_window_title = title;
if (monitor.window) {
SDL_SetWindowTitle(monitor.window, _window_title);
}
}
void Panel_sdl::_update_scaling(monitor_t *mon, float sx, float sy)
{
mon->scaling_x = sx;
mon->scaling_y = sy;
int nw = mon->frame_width;
int nh = mon->frame_height;
if (mon->frame_rotation & 1) {
std::swap(nw, nh);
}
int x, y, w, h;
int rw, rh;
SDL_GetRendererOutputSize(mon->renderer, &rw, &rh);
SDL_GetWindowSize(mon->window, &w, &h);
nw = nw * sx * w / rw;
nh = nh * sy * h / rh;
SDL_GetWindowPosition(mon->window, &x, &y);
SDL_SetWindowSize(mon->window, nw, nh);
SDL_SetWindowPosition(mon->window, x + (w - nw) / 2, y + (h - nh) / 2);
mon->panel->sdl_invalidate();
}
void Panel_sdl::sdl_create(monitor_t *m)
{
int flag = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
#if SDL_FULLSCREEN
flag |= SDL_WINDOW_FULLSCREEN;
#endif
if (m->frame_width < _cfg.panel_width) {
m->frame_width = _cfg.panel_width;
}
if (m->frame_height < _cfg.panel_height) {
m->frame_height = _cfg.panel_height;
}
int window_width = m->frame_width * m->scaling_x;
int window_height = m->frame_height * m->scaling_y;
int scaling_x = m->scaling_x;
int scaling_y = m->scaling_y;
if (m->frame_rotation & 1) {
std::swap(window_width, window_height);
std::swap(scaling_x, scaling_y);
}
{
m->window = SDL_CreateWindow(_window_title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height,
flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/
}
m->renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
m->texture =
SDL_CreateTexture(m->renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, _cfg.panel_width, _cfg.panel_height);
SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_NONE);
if (m->frame_image) {
// 枠画像用のサーフェイスを作成
auto sf = SDL_CreateRGBSurfaceFrom((void *)m->frame_image, m->frame_width, m->frame_height, 32, m->frame_width * 4,
0xFF000000, 0xFF0000, 0xFF00, 0xFF);
if (sf != nullptr) {
// 枠画像からテクスチャを作成
m->texture_frameimage = SDL_CreateTextureFromSurface(m->renderer, sf);
SDL_FreeSurface(sf);
}
}
SDL_SetTextureBlendMode(m->texture_frameimage, SDL_BLENDMODE_BLEND);
_update_scaling(m, scaling_x, scaling_y);
}
void Panel_sdl::sdl_update(void)
{
if (monitor.renderer == nullptr) {
sdl_create(&monitor);
}
bool step_exec = _in_step_exec;
if (_texupdate_counter != _modified_counter) {
pixelcopy_t pc(nullptr, color_depth_t::rgb888_3Byte, _write_depth, false);
if (_write_depth == rgb565_2Byte) {
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, swap565_t>;
} else if (_write_depth == rgb888_3Byte) {
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, bgr888_t>;
} else if (_write_depth == rgb332_1Byte) {
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, rgb332_t>;
} else if (_write_depth == grayscale_8bit) {
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, grayscale_t>;
}
if (0 == SDL_LockMutex(_sdl_mutex)) {
_texupdate_counter = _modified_counter;
for (int y = 0; y < _cfg.panel_height; ++y) {
pc.src_x32 = 0;
pc.src_data = _lines_buffer[y];
pc.fp_copy(&_texturebuf[y * _cfg.panel_width], 0, _cfg.panel_width, &pc);
}
SDL_UnlockMutex(_sdl_mutex);
SDL_UpdateTexture(monitor.texture, nullptr, _texturebuf, _cfg.panel_width * sizeof(rgb888_t));
}
}
int angle = monitor.frame_angle;
int target = (monitor.frame_rotation) * 90;
angle = (((target * 4) + (angle * 4) + (angle < target ? 8 : 0)) >> 3);
if (monitor.frame_angle != angle) { // 表示する向きを変える
monitor.frame_angle = angle;
sdl_invalidate();
} else if (monitor.frame_rotation & ~3u) {
monitor.frame_rotation &= 3;
monitor.frame_angle = (monitor.frame_rotation) * 90;
sdl_invalidate();
}
if (_invalidated || (_display_counter != _texupdate_counter)) {
SDL_RendererInfo info;
if (0 == SDL_GetRendererInfo(monitor.renderer, &info)) {
// ステップ実行中はVSYNCを待機しない
if (((bool)(info.flags & SDL_RENDERER_PRESENTVSYNC)) == step_exec) {
SDL_RenderSetVSync(monitor.renderer, !step_exec);
}
}
{
int red = 0;
int green = 0;
int blue = 0;
#if defined(M5GFX_BACK_COLOR)
red = ((M5GFX_BACK_COLOR) >> 16) & 0xFF;
green = ((M5GFX_BACK_COLOR) >> 8) & 0xFF;
blue = ((M5GFX_BACK_COLOR)) & 0xFF;
#endif
SDL_SetRenderDrawColor(monitor.renderer, red, green, blue, 0xFF);
}
SDL_RenderClear(monitor.renderer);
if (_invalidated) {
_invalidated = false;
int mw, mh;
SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh);
}
render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height, angle);
render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle);
SDL_RenderPresent(monitor.renderer);
_display_counter = _texupdate_counter;
if (_invalidated) {
_invalidated = false;
SDL_SetRenderDrawColor(monitor.renderer, 0, 0, 0, 0xFF);
SDL_RenderClear(monitor.renderer);
render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height,
angle);
render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle);
SDL_RenderPresent(monitor.renderer);
}
}
}
void Panel_sdl::render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle)
{
SDL_Point pivot;
pivot.x = (monitor.frame_width / 2.0f - tx) * (float)monitor.scaling_x;
pivot.y = (monitor.frame_height / 2.0f - ty) * (float)monitor.scaling_y;
SDL_Rect dstrect;
dstrect.w = tw * monitor.scaling_x;
dstrect.h = th * monitor.scaling_y;
int mw, mh;
SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh);
dstrect.x = mw / 2.0f - pivot.x;
dstrect.y = mh / 2.0f - pivot.y;
SDL_RenderCopyEx(monitor.renderer, texture, nullptr, &dstrect, angle, &pivot, SDL_RendererFlip::SDL_FLIP_NONE);
}
bool Panel_sdl::initFrameBuffer(size_t width, size_t height)
{
uint8_t **lineArray = (uint8_t **)heap_alloc_dma(height * sizeof(uint8_t *));
if (nullptr == lineArray) {
return false;
}
_texturebuf = (rgb888_t *)heap_alloc_dma(width * height * sizeof(rgb888_t));
/// 8byte alignment;
width = (width + 7) & ~7u;
_lines_buffer = lineArray;
memset(lineArray, 0, height * sizeof(uint8_t *));
uint8_t *framebuffer = (uint8_t *)heap_alloc_dma(width * height + 16);
auto fb = framebuffer;
{
for (size_t y = 0; y < height; ++y) {
lineArray[y] = fb;
fb += width;
}
}
return true;
}
void Panel_sdl::deinitFrameBuffer(void)
{
auto lines = _lines_buffer;
_lines_buffer = nullptr;
if (lines != nullptr) {
heap_free(lines[0]);
heap_free(lines);
}
if (_texturebuf) {
heap_free(_texturebuf);
_texturebuf = nullptr;
}
}
//----------------------------------------------------------------------------
} // namespace v1
} // namespace lgfx
#endif

View File

@@ -1,166 +0,0 @@
/*----------------------------------------------------------------------------/
Lovyan GFX - Graphics library for embedded devices.
Original Source:
https://github.com/lovyan03/LovyanGFX/
Licence:
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
Author:
[lovyan03](https://twitter.com/lovyan03)
Contributors:
[ciniml](https://github.com/ciniml)
[mongonta0716](https://github.com/mongonta0716)
[tobozo](https://github.com/tobozo)
Porting for SDL:
[imliubo](https://github.com/imliubo)
/----------------------------------------------------------------------------*/
#pragma once
#define SDL_MAIN_HANDLED
// cppcheck-suppress preprocessorErrorDirective
#if __has_include(<SDL2/SDL.h>)
#include <SDL2/SDL.h>
#include <SDL2/SDL_main.h>
#elif __has_include(<SDL.h>)
#include <SDL.h>
#include <SDL_main.h>
#endif
#if defined(SDL_h_)
#include "lgfx/v1/Touch.hpp"
#include "lgfx/v1/misc/range.hpp"
#include "lgfx/v1/panel/Panel_FrameBufferBase.hpp"
#include <cstdint>
namespace lgfx
{
inline namespace v1
{
struct Panel_sdl;
struct monitor_t {
SDL_Window *window = nullptr;
SDL_Renderer *renderer = nullptr;
SDL_Texture *texture = nullptr;
SDL_Texture *texture_frameimage = nullptr;
Panel_sdl *panel = nullptr;
// 外枠
const void *frame_image = 0;
uint_fast16_t frame_width = 0;
uint_fast16_t frame_height = 0;
uint_fast16_t frame_inner_x = 0;
uint_fast16_t frame_inner_y = 0;
int_fast16_t frame_rotation = 0;
int_fast16_t frame_angle = 0;
float scaling_x = 1;
float scaling_y = 1;
int_fast16_t touch_x, touch_y;
bool touched = false;
bool closing = false;
};
//----------------------------------------------------------------------------
struct Touch_sdl : public ITouch {
bool init(void) override { return true; }
void wakeup(void) override {}
void sleep(void) override {}
bool isEnable(void) override { return true; };
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override { return 0; }
};
//----------------------------------------------------------------------------
struct Panel_sdl : public Panel_FrameBufferBase {
static constexpr size_t EMULATED_GPIO_MAX = 128;
static volatile uint8_t _gpio_dummy_values[EMULATED_GPIO_MAX];
public:
Panel_sdl(void);
virtual ~Panel_sdl(void);
bool init(bool use_reset) override;
color_depth_t setColorDepth(color_depth_t depth) override;
void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override;
// void setInvert(bool invert) override {}
void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override;
void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override;
void writeBlock(uint32_t rawcolor, uint32_t length) override;
void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param,
bool use_dma) override;
void writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param) override;
void writePixels(pixelcopy_t *param, uint32_t len, bool use_dma) override;
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override;
void setWindowTitle(const char *title);
void setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y);
void setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y);
void setFrameRotation(uint_fast16_t frame_rotaion);
void setBrightness(uint8_t brightness) override{};
static volatile void gpio_hi(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 1; }
static volatile void gpio_lo(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 0; }
static volatile bool gpio_in(uint32_t pin) { return _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)]; }
static int setup(void);
static int loop(void);
static int close(void);
static int main(int (*fn)(bool *), uint32_t msec_step_exec = 512);
static void setShortcutKeymod(SDL_Keymod keymod) { _keymod = keymod; }
struct KeyCodeMapping_t {
SDL_KeyCode keycode = SDLK_UNKNOWN;
uint8_t gpio = 0;
};
static void addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio);
static int getKeyCodeMapping(SDL_KeyCode keyCode);
protected:
const char *_window_title = "LGFX Simulator";
SDL_mutex *_sdl_mutex = nullptr;
void sdl_create(monitor_t *m);
void sdl_update(void);
touch_point_t _touch_point;
monitor_t monitor;
rgb888_t *_texturebuf = nullptr;
uint_fast16_t _modified_counter;
uint_fast16_t _texupdate_counter;
uint_fast16_t _display_counter;
bool _invalidated;
static void _event_proc(void);
static void _update_proc(void);
static void _update_scaling(monitor_t *m, float sx, float sy);
void sdl_invalidate(void) { _invalidated = true; }
void render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle);
bool initFrameBuffer(size_t width, size_t height);
void deinitFrameBuffer(void);
static SDL_Keymod _keymod;
struct lock_t {
lock_t(Panel_sdl *parent);
~lock_t();
protected:
Panel_sdl *_parent;
};
};
//----------------------------------------------------------------------------
} // namespace v1
} // namespace lgfx
#endif

View File

@@ -100,7 +100,7 @@ namespace graphics
#define NUM_EXTRA_FRAMES 3 // text message and debug frame
// if defined a pixel will blink to show redraws
// #define SHOW_REDRAWS
#define ASCII_BELL '\x07'
// A text message frame + debug frame + all the node infos
FrameCallback *normalFrames;
static uint32_t targetFramerate = IDLE_FRAMERATE;
@@ -453,7 +453,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#endif
dispdev->displayOn();
#if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2)
#ifdef HELTEC_TRACKER_V1_X
ui->init();
#endif
#ifdef USE_ST7789
@@ -1428,9 +1428,6 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
}
nodeDB->updateGUI = false;
break;
case STATUS_TYPE_POWER:
forceDisplay(true);
break;
}
return 0;
@@ -1461,36 +1458,28 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
}
// === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const meshtastic_Channel channel =
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
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;
if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_bell_vibra ||
moduleConfig.external_notification.alert_bell_buzzer)
// Check for bell character to determine if this message is an alert
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == ASCII_BELL) {
if (msgRaw[i] == '\x07') {
isAlert = true;
break;
}
}
// Unlike generic messages, alerts (when enabled via the ext notif module) ignore any
// 'mute' preferences set to any specific node or channel.
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
}
screen->showSimpleBanner(banner, 3000);
} else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
} else {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
@@ -1501,23 +1490,16 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
} else {
strcpy(banner, "New Message");
}
}
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
(!isBroadcast(packet->to) && isToUs(packet))) {
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
// - packet contains an alert and alert bell buzzer is enabled
// - packet is a non-broadcast that is addressed to this node
playLongBeep();
}
#else
screen->showSimpleBanner(banner, 3000);
#endif
}
}
}
return 0;
}

View File

@@ -751,8 +751,10 @@ static LGFX *tft = nullptr;
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
#elif ARCH_PORTDUINO
#include "Panel_sdl.hpp"
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
#if defined(LGFX_SDL)
#include <lgfx/v1/platforms/sdl/Panel_sdl.hpp>
#endif
class LGFX : public lgfx::LGFX_Device
{
@@ -781,10 +783,10 @@ class LGFX : public lgfx::LGFX_Device
_panel_instance = new lgfx::Panel_ILI9488;
else if (portduino_config.displayPanel == hx8357d)
_panel_instance = new lgfx::Panel_HX8357D;
#if defined(SDL_h_)
else if (portduino_config.displayPanel == x11)
#if defined(LGFX_SDL)
else if (portduino_config.displayPanel == x11) {
_panel_instance = new lgfx::Panel_sdl;
}
#endif
else {
_panel_instance = new lgfx::Panel_NULL;
@@ -798,7 +800,6 @@ class LGFX : public lgfx::LGFX_Device
buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable)
_bus_instance.config(buscfg); // applies the set value to the bus.
if (portduino_config.displayPanel != x11)
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
@@ -847,7 +848,7 @@ class LGFX : public lgfx::LGFX_Device
_touch_instance->config(touch_cfg);
_panel_instance->setTouch(_touch_instance);
}
#if defined(SDL_h_)
#if defined(LGFX_SDL)
if (portduino_config.displayPanel == x11) {
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
sdl_panel_->setup();
@@ -1236,7 +1237,7 @@ void TFTDisplay::display(bool fromBlank)
void TFTDisplay::sdlLoop()
{
#if defined(SDL_h_)
#if defined(LGFX_SDL)
static int lastPressed = 0;
static int shuttingDown = false;
if (portduino_config.displayPanel == x11) {
@@ -1246,26 +1247,27 @@ void TFTDisplay::sdlLoop()
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
}
// debounce
if (lastPressed != 0 && !sdl_panel_->gpio_in(lastPressed))
if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed))
return;
if (!sdl_panel_->gpio_in(37)) {
if (!lgfx::v1::gpio_in(37)) {
lastPressed = 37;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!sdl_panel_->gpio_in(36)) {
} else if (!lgfx::v1::gpio_in(36)) {
lastPressed = 36;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!sdl_panel_->gpio_in(38)) {
} else if (!lgfx::v1::gpio_in(38)) {
lastPressed = 38;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!sdl_panel_->gpio_in(39)) {
} else if (!lgfx::v1::gpio_in(39)) {
lastPressed = 39;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!sdl_panel_->gpio_in(SDL_SCANCODE_KP_ENTER)) {
} else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) {
lastPressed = SDL_SCANCODE_KP_ENTER;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);

View File

@@ -116,8 +116,6 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) {
config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected);
auto changes = SEGMENT_CONFIG;
// This is needed as we wait til picking the LoRa region to generate keys for the first time.
if (!owner.is_licensed) {
bool keygenSuccess = false;
@@ -126,7 +124,6 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
keygenSuccess = true;
}
} else {
LOG_INFO("Generate new PKI keys");
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
@@ -144,14 +141,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
if (myRegion->dutyCycle < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
}
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
// Default broker is in use, so subscribe to the appropriate MQTT root topic for this region
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
changes |= SEGMENT_MODULECONFIG;
}
service->reloadConfig(changes);
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
}
};
@@ -762,31 +752,6 @@ void menuHandler::nodeListMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::nodeNameLengthMenu()
{
enum OptionsNumbers { Back, Long, Short };
static const char *optionsArray[] = {"Back", "Long", "Short"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Name Length";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Long) {
// Set names to long
LOG_INFO("Setting names to long");
config.display.use_long_node_name = true;
} else if (selected == Short) {
// Set names to short
LOG_INFO("Setting names to short");
config.display.use_long_node_name = false;
} else if (selected == Back) {
menuQueue = screen_options_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::resetNodeDBMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
@@ -887,31 +852,24 @@ void menuHandler::GPSFormatMenu()
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 2) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 3) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 4) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 5) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 6) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 7) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else {
menuQueue = position_base_menu;
@@ -946,11 +904,11 @@ void menuHandler::BluetoothToggleMenu()
void menuHandler::BuzzerModeMenu()
{
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only", "DMs Only"};
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Buzzer Mode";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 5;
bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void {
config.device.buzzer_mode = (meshtastic_Config_DeviceConfig_BuzzerMode)selected;
service->reloadConfig(SEGMENT_CONFIG);
@@ -1329,16 +1287,11 @@ void menuHandler::screenOptionsMenu()
hasSupportBrightness = false;
#endif
enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor };
static const char *optionsArray[5] = {"Back"};
static int optionsEnumArray[5] = {Back};
enum optionsNumbers { Back, Brightness, ScreenColor };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
#if defined(T_DECK) || defined(T_LORA_PAGER)
optionsArray[options] = "Show Long/Short Name";
optionsEnumArray[options++] = NodeNameLength;
#endif
// Only show brightness for B&W displays
if (hasSupportBrightness) {
optionsArray[options] = "Brightness";
@@ -1363,9 +1316,6 @@ void menuHandler::screenOptionsMenu()
} else if (selected == ScreenColor) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow();
} else if (selected == NodeNameLength) {
menuHandler::menuQueue = menuHandler::node_name_length_menu;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
@@ -1464,8 +1414,6 @@ void menuHandler::FrameToggles_menu()
lora,
clock,
show_favorites,
show_telemetry,
show_power,
enumEnd
};
static const char *optionsArray[enumEnd] = {"Finish"};
@@ -1504,12 +1452,6 @@ void menuHandler::FrameToggles_menu()
optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
optionsEnumArray[options++] = show_favorites;
optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Telemetry" : "Show Telemetry";
optionsEnumArray[options++] = show_telemetry;
optionsArray[options] = moduleConfig.telemetry.power_screen_enabled ? "Hide Power" : "Show Power";
optionsEnumArray[options++] = show_power;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Show/Hide Frames";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -1564,14 +1506,6 @@ void menuHandler::FrameToggles_menu()
screen->toggleFrameVisibility("show_favorites");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == show_telemetry) {
moduleConfig.telemetry.environment_screen_enabled = !moduleConfig.telemetry.environment_screen_enabled;
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == show_power) {
moduleConfig.telemetry.power_screen_enabled = !moduleConfig.telemetry.power_screen_enabled;
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
@@ -1643,9 +1577,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case brightness_picker:
BrightnessPickerMenu();
break;
case node_name_length_menu:
nodeNameLengthMenu();
break;
case reboot_menu:
rebootMenu();
break;

View File

@@ -43,7 +43,6 @@ class menuHandler
key_verification_final_prompt,
trace_route_menu,
throttle_message,
node_name_length_menu,
FrameToggles
};
static screenMenus menuQueue;
@@ -86,7 +85,6 @@ class menuHandler
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
static void nodeNameLengthMenu();
static void FrameToggles_menu();
static void textMessageMenu();

View File

@@ -55,32 +55,26 @@ static int scrollIndex = 0;
const char *getSafeNodeName(meshtastic_NodeInfoLite *node)
{
const char *name = NULL;
static char nodeName[16] = "?";
if (config.display.use_long_node_name == true) {
if (node->has_user && strlen(node->user.long_name) > 0) {
name = node->user.long_name;
} else {
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
}
} else {
if (node->has_user && strlen(node->user.short_name) > 0) {
name = node->user.short_name;
} else {
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
bool valid = true;
const char *name = node->user.short_name;
for (size_t i = 0; i < strlen(name); i++) {
uint8_t c = (uint8_t)name[i];
if (c < 32 || c > 126) {
valid = false;
break;
}
}
// Use sanitizeString() function and copy directly into nodeName
std::string sanitized_name = sanitizeString(name ? name : "");
if (!sanitized_name.empty()) {
strncpy(nodeName, sanitized_name.c_str(), sizeof(nodeName) - 1);
if (valid) {
strncpy(nodeName, name, sizeof(nodeName) - 1);
nodeName[sizeof(nodeName) - 1] = '\0';
} else {
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
}
} else {
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
}
return nodeName;
}

View File

@@ -125,10 +125,8 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
char displayLine[32];
if (!gps->getIsConnected() && !config.position.fixed_position) {
if (strcmp(mode, "line1") == 0) {
strcpy(displayLine, "No GPS present");
display->drawString(x, y, displayLine);
}
} else if (!gps->getHasLock() && !config.position.fixed_position) {
if (strcmp(mode, "line1") == 0) {
strcpy(displayLine, "No GPS Lock");
@@ -1105,18 +1103,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
// === Fourth Row: Line 2 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2");
}
// === Final Row: Altitude ===
char altitudeLine[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(altitudeLine, sizeof(altitudeLine), "Alt: %.0fft", alt * METERS_TO_FEET);
} else {
snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0im", alt);
}
display->drawString(x, getTextPositions(display)[line++], altitudeLine);
}
#if !defined(M5STACK_UNITC6L)
// === Draw Compass if heading is valid ===

View File

@@ -274,13 +274,8 @@ int32_t ButtonThread::runOnce()
}
}
btnEvent = BUTTON_EVENT_NONE;
// only pull when the button is pressed, we get notified via IRQ on a new press
if (!userButton.isIdle() || waitingForLongPress) {
return 50;
}
return 100; // FIXME: Why can't we rely on interrupts and use INT32_MAX here?
}
/*
* Attach (or re-attach) hardware interrupts for buttons

View File

@@ -3,66 +3,16 @@
InputBroker *inputBroker = nullptr;
InputBroker::InputBroker()
{
#ifdef HAS_FREE_RTOS
inputEventQueue = xQueueCreate(5, sizeof(InputEvent));
pollSoonQueue = xQueueCreate(5, sizeof(InputPollable *));
xTaskCreate(pollSoonWorker, "input-pollSoon", 2 * 1024, this, 10, &pollSoonTask);
#endif
}
InputBroker::InputBroker(){};
void InputBroker::registerSource(Observable<const InputEvent *> *source)
{
this->inputEventObserver.observe(source);
}
#ifdef HAS_FREE_RTOS
void InputBroker::requestPollSoon(InputPollable *pollable)
{
if (xPortInIsrContext() == pdTRUE) {
xQueueSendFromISR(pollSoonQueue, &pollable, NULL);
} else {
xQueueSend(pollSoonQueue, &pollable, 0);
}
}
void InputBroker::queueInputEvent(const InputEvent *event)
{
if (xPortInIsrContext() == pdTRUE) {
xQueueSendFromISR(inputEventQueue, event, NULL);
} else {
xQueueSend(inputEventQueue, event, portMAX_DELAY);
}
}
void InputBroker::processInputEventQueue()
{
InputEvent event;
while (xQueueReceive(inputEventQueue, &event, 0)) {
handleInputEvent(&event);
}
}
#endif
int InputBroker::handleInputEvent(const InputEvent *event)
{
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
this->notifyObservers(event);
return 0;
}
#ifdef HAS_FREE_RTOS
void InputBroker::pollSoonWorker(void *p)
{
InputBroker *instance = (InputBroker *)p;
while (true) {
InputPollable *pollable = NULL;
xQueueReceive(instance->pollSoonQueue, &pollable, portMAX_DELAY);
if (pollable) {
pollable->pollOnce();
}
}
vTaskDelete(NULL);
}
#endif

View File

@@ -1,7 +1,5 @@
#pragma once
#include "Observer.h"
#include "freertosinc.h"
enum input_broker_event {
INPUT_BROKER_NONE = 0,
@@ -43,13 +41,6 @@ typedef struct _InputEvent {
uint16_t touchX;
uint16_t touchY;
} InputEvent;
class InputPollable
{
public:
virtual void pollOnce() = 0;
};
class InputBroker : public Observable<const InputEvent *>
{
CallbackObserver<InputBroker, const InputEvent *> inputEventObserver =
@@ -59,22 +50,9 @@ class InputBroker : public Observable<const InputEvent *>
InputBroker();
void registerSource(Observable<const InputEvent *> *source);
void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
#ifdef HAS_FREE_RTOS
void requestPollSoon(InputPollable *pollable);
void queueInputEvent(const InputEvent *event);
void processInputEventQueue();
#endif
protected:
int handleInputEvent(const InputEvent *event);
private:
#ifdef HAS_FREE_RTOS
QueueHandle_t inputEventQueue;
QueueHandle_t pollSoonQueue;
TaskHandle_t pollSoonTask;
static void pollSoonWorker(void *p);
#endif
};
extern InputBroker *inputBroker;

View File

@@ -8,7 +8,7 @@
RotaryEncoderImpl *rotaryEncoderImpl;
RotaryEncoderImpl::RotaryEncoderImpl()
RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME)
{
rotary = nullptr;
}
@@ -18,6 +18,7 @@ bool RotaryEncoderImpl::init()
if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
moduleConfig.canned_message.inputbroker_pin_b == 0) {
// Input device is disabled.
disable();
return false;
}
@@ -29,11 +30,7 @@ bool RotaryEncoderImpl::init()
moduleConfig.canned_message.inputbroker_pin_press);
rotary->resetButton();
interruptInstance = this;
auto interruptHandler = []() { inputBroker->requestPollSoon(interruptInstance); };
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
inputBroker->registerSource(this);
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
@@ -41,36 +38,36 @@ bool RotaryEncoderImpl::init()
return true;
}
void RotaryEncoderImpl::pollOnce()
int32_t RotaryEncoderImpl::runOnce()
{
InputEvent e{ORIGIN_NAME, INPUT_BROKER_NONE, 0, 0, 0};
InputEvent e{originName, INPUT_BROKER_NONE, 0, 0, 0};
static uint32_t lastPressed = millis();
if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
if (lastPressed + 200 < millis()) {
LOG_DEBUG("Rotary event Press");
lastPressed = millis();
e.inputEvent = this->eventPressed;
inputBroker->queueInputEvent(&e);
}
}
} else {
switch (rotary->process()) {
case RotaryEncoder::DIRECTION_CW:
LOG_DEBUG("Rotary event CW");
e.inputEvent = this->eventCw;
inputBroker->queueInputEvent(&e);
break;
case RotaryEncoder::DIRECTION_CCW:
LOG_DEBUG("Rotary event CCW");
e.inputEvent = this->eventCcw;
inputBroker->queueInputEvent(&e);
break;
default:
break;
}
}
RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
if (e.inputEvent != INPUT_BROKER_NONE) {
this->notifyObservers(&e);
}
return 10;
}
#endif

View File

@@ -1,6 +1,6 @@
#pragma once
// This is a version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
#include "InputBroker.h"
#include "concurrency/OSThread.h"
@@ -8,21 +8,21 @@
class RotaryEncoder;
class RotaryEncoderImpl : public InputPollable
class RotaryEncoderImpl : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
RotaryEncoderImpl();
bool init(void);
virtual void pollOnce() override;
protected:
static RotaryEncoderImpl *interruptInstance;
virtual int32_t runOnce() override;
input_broker_event eventCw = INPUT_BROKER_NONE;
input_broker_event eventCcw = INPUT_BROKER_NONE;
input_broker_event eventPressed = INPUT_BROKER_NONE;
RotaryEncoder *rotary;
const char *originName;
};
extern RotaryEncoderImpl *rotaryEncoderImpl;

View File

@@ -23,7 +23,6 @@
#include "power.h"
#if !MESHTASTIC_EXCLUDE_I2C
#include "detect/ScanI2CConsumer.h"
#include "detect/ScanI2CTwoWire.h"
#include <Wire.h>
#endif
@@ -298,12 +297,6 @@ void printInfo()
#ifndef PIO_UNIT_TESTING
void setup()
{
#if defined(R1_NEO)
pinMode(DCDC_EN_HOLD, OUTPUT);
digitalWrite(DCDC_EN_HOLD, HIGH);
pinMode(NRF_ON, OUTPUT);
digitalWrite(NRF_ON, HIGH);
#endif
#if defined(PIN_POWER_EN)
pinMode(PIN_POWER_EN, OUTPUT);
@@ -382,7 +375,7 @@ void setup()
io.pinMode(EXPANDS_DRV_EN, OUTPUT);
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
io.pinMode(EXPANDS_AMP_EN, OUTPUT);
io.digitalWrite(EXPANDS_AMP_EN, LOW);
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
io.pinMode(EXPANDS_LORA_EN, OUTPUT);
io.digitalWrite(EXPANDS_LORA_EN, HIGH);
io.pinMode(EXPANDS_GPS_EN, OUTPUT);
@@ -719,21 +712,46 @@ void setup()
LOG_DEBUG("acc_info = %i", acc_info.type);
#endif
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BME_680, meshtastic_TelemetrySensorType_BME680);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BME_280, meshtastic_TelemetrySensorType_BME280);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_280, meshtastic_TelemetrySensorType_BMP280);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_3XX, meshtastic_TelemetrySensorType_BMP3XX);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHT31, meshtastic_TelemetrySensorType_SHT31);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHTC3, meshtastic_TelemetrySensorType_SHTC3);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LPS22HB, meshtastic_TelemetrySensorType_LPS22);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::OPT3001, meshtastic_TelemetrySensorType_OPT3001);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90632, meshtastic_TelemetrySensorType_MLX90632);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::CGRADSENS, meshtastic_TelemetrySensorType_RADSENS);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN, meshtastic_TelemetrySensorType_DFROBOT_RAIN);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LTR390UV, meshtastic_TelemetrySensorType_LTR390UV);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DPS310, meshtastic_TelemetrySensorType_DPS310);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2561, meshtastic_TelemetrySensorType_TSL2561);
i2cScanner.reset();
#endif
#ifdef HAS_SDCARD
@@ -775,6 +793,13 @@ void setup()
}
#endif
// If we're taking on the repeater role, use NextHopRouter and turn off 3V3_S rail because peripherals are not needed
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
router = new NextHopRouter();
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, LOW);
#endif
} else
router = new ReliableRouter();
// only play start melody when role is not tracker or sensor
@@ -901,7 +926,8 @@ void setup()
if (sensor_detected == false) {
#endif
if (HAS_GPS) {
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
gps = GPS::createGps();
if (gps) {
gpsStatus->observe(&gps->newStatus);
@@ -940,12 +966,6 @@ void setup()
// Now that the mesh service is created, create any modules
setupModules();
#if !MESHTASTIC_EXCLUDE_I2C
// Inform modules about I2C devices
ScanI2CCompleted(i2cScanner.get());
i2cScanner.reset();
#endif
// warn the user about a low entropy key
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
LOG_WARN(LOW_ENTROPY_WARNING);
@@ -982,7 +1002,6 @@ void setup()
config.pullupSense = INPUT_PULLUP;
config.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1003,7 +1022,6 @@ void setup()
touchConfig.pullupSense = pullup_sense;
touchConfig.intRoutine = []() {
TouchButtonThread->userButton.tick();
TouchButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1023,7 +1041,6 @@ void setup()
cancelConfig.pullupSense = pullup_sense;
cancelConfig.intRoutine = []() {
CancelButtonThread->userButton.tick();
CancelButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1044,7 +1061,6 @@ void setup()
backConfig.pullupSense = pullup_sense;
backConfig.intRoutine = []() {
BackButtonThread->userButton.tick();
BackButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1079,7 +1095,6 @@ void setup()
userConfig.pullupSense = pullup_sense;
userConfig.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1097,7 +1112,6 @@ void setup()
userConfigNoScreen.pullupSense = pullup_sense;
userConfigNoScreen.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1582,13 +1596,8 @@ void loop()
#endif
service->loop();
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS)
if (inputBroker)
inputBroker->processInputEventQueue();
#endif
#if ARCH_PORTDUINO && HAS_TFT
if (screen && portduino_config.displayPanel == x11 &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(LGFX_SDL)
if (screen) {
auto dispdev = screen->getDisplayDevice();
if (dispdev)
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
@@ -1598,9 +1607,6 @@ void loop()
// We want to sleep as long as possible here - because it saves power
if (!runASAP && loopCanSleep()) {
#ifdef DEBUG_LOOP_TIMING
LOG_DEBUG("main loop delay: %d", delayMsec);
#endif
mainDelay.delay(delayMsec);
}
}

View File

@@ -54,10 +54,6 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
// We already enqueued the improved copy, so make sure the incoming packet stops here.
return true;
}
// No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
// delivering the same packet to applications/phone twice with different hop limits.
seenRecently = true;
}
if (seenRecently) {
@@ -85,8 +81,9 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *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, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
// 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;
}
@@ -105,7 +102,7 @@ bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
{
if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA && roleAllowsCancelingDupe(p)) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router!
// 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))
txRelayCanceled++;
@@ -146,7 +143,8 @@ void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
LOG_INFO("Rebroadcast received floodmsg");
// Note: we are careful to resend using the original senders node id
send(tosend);
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);
} else {
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
}

View File

@@ -59,7 +59,7 @@ class FloodingRouter : public Router
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
// Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of
// 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);

View File

@@ -181,6 +181,9 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
err = lora.setOutputPower(power);
assert(err == RADIOLIB_ERR_NONE);
// Initialize AGC/AFC reset timing (30 second interval)
nextAgcResetMs = millis() + THIRY_SECONDS_MS;
startReceive(); // restart receiving
return RADIOLIB_ERR_NONE;
@@ -218,7 +221,6 @@ template <typename T> void LR11x0Interface<T>::addReceiveMetadata(meshtastic_Mes
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
}
/** We override to turn on transmitter power as needed.

View File

@@ -65,7 +65,5 @@ template <class T> class LR11x0Interface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
};
#endif

View File

@@ -65,7 +65,7 @@ void fixPriority(meshtastic_MeshPacket *p)
}
/** enqueue a packet, return false if full */
bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p, bool *dropped)
bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p)
{
// no space - try to replace a lower priority packet in the queue
if (queue.size() >= maxLen) {
@@ -73,16 +73,9 @@ bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p, bool *dropped)
if (!replaced) {
LOG_WARN("TX queue is full, and there is no lower-priority packet available to evict in favour of 0x%08x", p->id);
}
if (dropped) {
*dropped = true;
}
return replaced;
}
if (dropped) {
*dropped = false;
}
// Find the correct position using upper_bound to maintain a stable order
auto it = std::upper_bound(queue.begin(), queue.end(), p, CompareMeshPacketFunc);
queue.insert(it, p); // Insert packet at the found position

View File

@@ -19,10 +19,8 @@ class MeshPacketQueue
public:
explicit MeshPacketQueue(size_t _maxLen);
/** enqueue a packet, return false if full
* @param dropped Optional pointer to a bool that will be set to true if a packet was dropped
*/
bool enqueue(meshtastic_MeshPacket *p, bool *dropped = nullptr);
/** enqueue a packet, return false if full */
bool enqueue(meshtastic_MeshPacket *p);
/** return true if the queue is empty */
bool empty();

View File

@@ -85,10 +85,11 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping
nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
bool isPreferredRebroadcaster = config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER;
bool isPreferredRebroadcaster =
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_REPEATER);
if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo");
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo"); // because this potentially a Repeater which will
// ignore our request for its NodeInfo
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) {

View File

@@ -64,10 +64,6 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
// We already enqueued the improved copy, so make sure the incoming packet stops here.
return true;
}
// No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
// delivering the same packet to applications/phone twice with different hop limits.
seenRecently = true;
}
if (seenRecently) {
@@ -114,14 +110,11 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
if (origTx) {
// Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came directly
// from the destination
bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
bool weWereSoleRelayer = false;
bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
if ((weWereRelayer && wasAlreadyRelayer) ||
(p->hop_start != 0 && p->hop_start == p->hop_limit && weWereSoleRelayer)) {
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
(p->hop_start != 0 && p->hop_start == p->hop_limit &&
wasSoleRelayer(ourRelayID, p->decoded.request_id, p->to))) {
if (origTx->next_hop != p->relay_node) { // Not already set
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply (was relayer %d we were sole %d)", p->from,
p->relay_node, wasAlreadyRelayer, weWereSoleRelayer);
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
origTx->next_hop = p->relay_node;
}
}
@@ -174,6 +167,7 @@ bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
*/
uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
{
// When we're a repeater router->sniffReceived will call NextHopRouter directly without checking for broadcast
if (isBroadcast(to))
return NO_NEXT_HOP_PREFERENCE;
@@ -211,7 +205,7 @@ bool NextHopRouter::roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *
{
// 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, ROUTER_LATE which should always transmit the packet at least 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
}
@@ -224,7 +218,7 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
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)
// We only cancel it if we are the original sender or if we're not a router(_late)/repeater
if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id);

View File

@@ -256,8 +256,6 @@ NodeDB::NodeDB()
owner.role = config.device.role;
// Ensure macaddr is set to our macaddr as it will be copied in our info below
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
// Ensure owner.id is always derived from the node number
snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum());
if (!config.has_security) {
config.has_security = true;
@@ -556,9 +554,10 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#endif
#ifdef USERPREFS_CONFIG_DEVICE_ROLE
// Restrict ROUTER*, LOST AND FOUND roles for security reasons
// Restrict ROUTER*, LOST AND FOUND, and REPEATER roles for security reasons
if (IS_ONE_OF(USERPREFS_CONFIG_DEVICE_ROLE, meshtastic_Config_DeviceConfig_Role_ROUTER,
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND)) {
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_REPEATER,
meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND)) {
LOG_WARN("ROUTER roles are restricted, falling back to CLIENT role");
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
} else {
@@ -702,7 +701,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#ifdef USERPREFS_NETWORK_ENABLED_PROTOCOLS
config.network.enabled_protocols = USERPREFS_NETWORK_ENABLED_PROTOCOLS;
#else
config.network.enabled_protocols = 0;
config.network.enabled_protocols = 1;
#endif
#endif
@@ -907,6 +906,11 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
moduleConfig.telemetry.device_update_interval = ONE_DAY;
owner.has_is_unmessagable = true;
owner.is_unmessagable = true;
} else if (role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
owner.has_is_unmessagable = true;
owner.is_unmessagable = true;
config.display.screen_on_secs = 1;
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY;
} else if (role == meshtastic_Config_DeviceConfig_Role_SENSOR) {
owner.has_is_unmessagable = true;
owner.is_unmessagable = true;
@@ -1154,20 +1158,6 @@ void NodeDB::loadFromDisk()
spiLock->unlock();
#endif
#ifdef FSCom
#ifdef FACTORY_INSTALL
spiLock->lock();
if (!FSCom.exists("/prefs/" xstr(BUILD_EPOCH))) {
LOG_WARN("Factory Install Reset!");
FSCom.format();
FSCom.mkdir("/prefs");
File f2 = FSCom.open("/prefs/" xstr(BUILD_EPOCH), FILE_O_WRITE);
if (f2) {
f2.flush();
f2.close();
}
}
spiLock->unlock();
#endif
spiLock->lock();
if (FSCom.exists(legacyPrefFileName)) {
spiLock->unlock();
@@ -1613,18 +1603,9 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
void NodeDB::addFromContact(meshtastic_SharedContact contact)
{
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
if (!info || !contact.has_user) {
if (!info) {
return;
}
// If the local node has this node marked as manually verified
// and the client does not, do not allow the client to update the
// saved public key.
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
if (contact.user.public_key.size != info->user.public_key.size ||
memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
return;
}
}
info->num = contact.node_num;
info->has_user = true;
info->user = TypeConversions::ConvertToUserLite(contact.user);
@@ -1639,12 +1620,10 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
} else {
info->last_heard = getValidTime(RTCQualityNTP);
info->is_favorite = true;
// As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
if (contact.manually_verified) {
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
// Mark the node's key as manually verified to indicate trustworthiness.
updateGUIforNode = info;
// powerFSM.trigger(EVENT_NODEDB_UPDATED); This event has been retired
sortMeshDB();
notifyObservers(true); // Force an update whether or not our node counts have changed
}
@@ -1688,14 +1667,14 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
return false;
}
LOG_INFO("Public Key set for node, not updating!");
// we copy the key into the incoming packet, to prevent overwrite
p.public_key.size = 32;
memcpy(p.public_key.bytes, info->user.public_key.bytes, 32);
} else if (p.public_key.size == 32) {
LOG_INFO("Update Node Pubkey!");
}
#endif
// Always ensure user.id is derived from nodeId, regardless of what was received
snprintf(p.id, sizeof(p.id), "!%08x", nodeId);
// Both of info->user and p start as filled with zero so I think this is okay
auto lite = TypeConversions::ConvertToUserLite(p);
bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex);
@@ -1874,13 +1853,6 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
return info->channel;
}
std::string NodeDB::getNodeId() const
{
char nodeId[16];
snprintf(nodeId, sizeof(nodeId), "!%08x", myNodeInfo.my_node_num);
return std::string(nodeId);
}
/// Find a node in our DB, return null for missing
/// NOTE: This function might be called from an ISR
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)

View File

@@ -5,7 +5,6 @@
#include <algorithm>
#include <assert.h>
#include <pb_encode.h>
#include <string>
#include <vector>
#include "MeshTypes.h"
@@ -204,9 +203,6 @@ class NodeDB
/// @return our node number
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
/// @return our node ID as a string in the format "!xxxxxxxx"
std::string getNodeId() const;
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }

View File

@@ -1,253 +0,0 @@
#include "PacketCache.h"
#include "Router.h"
PacketCache packetCache{};
/**
* Allocate a new cache entry and copy the packet header and payload into it
*/
PacketCacheEntry *PacketCache::cache(const meshtastic_MeshPacket *p, bool preserveMetadata)
{
size_t payload_size =
(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) ? p->encrypted.size : p->decoded.payload.size;
PacketCacheEntry *e = (PacketCacheEntry *)malloc(sizeof(PacketCacheEntry) + payload_size +
(preserveMetadata ? sizeof(PacketCacheMetadata) : 0));
if (!e) {
LOG_ERROR("Unable to allocate memory for packet cache entry");
return NULL;
}
*e = {};
e->header.from = p->from;
e->header.to = p->to;
e->header.id = p->id;
e->header.channel = p->channel;
e->header.next_hop = p->next_hop;
e->header.relay_node = p->relay_node;
e->header.flags = (p->hop_limit & PACKET_FLAGS_HOP_LIMIT_MASK) | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) |
(p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0) |
((p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK);
PacketCacheMetadata m{};
if (preserveMetadata) {
e->has_metadata = true;
m.rx_rssi = (uint8_t)(p->rx_rssi + 200);
m.rx_snr = (uint8_t)((p->rx_snr + 30.0f) / 0.25f);
m.rx_time = p->rx_time;
m.transport_mechanism = p->transport_mechanism;
m.priority = p->priority;
}
if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
e->encrypted = true;
e->payload_len = p->encrypted.size;
memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->encrypted.bytes, p->encrypted.size);
} else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
e->encrypted = false;
if (preserveMetadata) {
m.portnum = p->decoded.portnum;
m.want_response = p->decoded.want_response;
m.emoji = p->decoded.emoji;
m.bitfield = p->decoded.bitfield;
if (p->decoded.reply_id)
m.reply_id = p->decoded.reply_id;
else if (p->decoded.request_id)
m.request_id = p->decoded.request_id;
}
e->payload_len = p->decoded.payload.size;
memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->decoded.payload.bytes, p->decoded.payload.size);
} else {
LOG_ERROR("Unable to cache packet with unknown payload type %d", p->which_payload_variant);
free(e);
return NULL;
}
if (preserveMetadata)
memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry) + e->payload_len, &m, sizeof(m));
size += sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0);
insert(e);
return e;
};
/**
* Dump a list of packets into the provided buffer
*/
void PacketCache::dump(void *dest, const PacketCacheEntry **entries, size_t num_entries)
{
unsigned char *pos = (unsigned char *)dest;
for (size_t i = 0; i < num_entries; i++) {
size_t entry_len =
sizeof(PacketCacheEntry) + entries[i]->payload_len + (entries[i]->has_metadata ? sizeof(PacketCacheMetadata) : 0);
memcpy(pos, entries[i], entry_len);
pos += entry_len;
}
}
/**
* Calculate the length of buffer needed to dump the specified entries
*/
size_t PacketCache::dumpSize(const PacketCacheEntry **entries, size_t num_entries)
{
size_t total_size = 0;
for (size_t i = 0; i < num_entries; i++) {
total_size += sizeof(PacketCacheEntry) + entries[i]->payload_len;
if (entries[i]->has_metadata)
total_size += sizeof(PacketCacheMetadata);
}
return total_size;
}
/**
* Find a packet in the cache
*/
PacketCacheEntry *PacketCache::find(NodeNum from, PacketId id)
{
uint16_t h = PACKET_HASH(from, id);
PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)];
while (e) {
if (e->header.from == from && e->header.id == id)
return e;
e = e->next;
}
return NULL;
}
/**
* Find a packet in the cache by its hash
*/
PacketCacheEntry *PacketCache::find(PacketHash h)
{
PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)];
while (e) {
if (PACKET_HASH(e->header.from, e->header.id) == h)
return e;
e = e->next;
}
return NULL;
}
/**
* Load a list of packets from the provided buffer
*/
bool PacketCache::load(void *src, PacketCacheEntry **entries, size_t num_entries)
{
memset(entries, 0, sizeof(PacketCacheEntry *) * num_entries);
unsigned char *pos = (unsigned char *)src;
for (size_t i = 0; i < num_entries; i++) {
PacketCacheEntry e{};
memcpy(&e, pos, sizeof(PacketCacheEntry));
size_t entry_len = sizeof(PacketCacheEntry) + e.payload_len + (e.has_metadata ? sizeof(PacketCacheMetadata) : 0);
entries[i] = (PacketCacheEntry *)malloc(entry_len);
size += entry_len;
if (!entries[i]) {
LOG_ERROR("Unable to allocate memory for packet cache entry");
for (size_t j = 0; j < i; j++) {
size -= sizeof(PacketCacheEntry) + entries[j]->payload_len +
(entries[j]->has_metadata ? sizeof(PacketCacheMetadata) : 0);
free(entries[j]);
entries[j] = NULL;
}
return false;
}
memcpy(entries[i], pos, entry_len);
pos += entry_len;
}
for (size_t i = 0; i < num_entries; i++)
insert(entries[i]);
return true;
}
/**
* Copy the cached packet into the provided MeshPacket structure
*/
void PacketCache::rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p)
{
if (!e || !p)
return;
*p = {};
p->from = e->header.from;
p->to = e->header.to;
p->id = e->header.id;
p->channel = e->header.channel;
p->next_hop = e->header.next_hop;
p->relay_node = e->header.relay_node;
p->hop_limit = e->header.flags & PACKET_FLAGS_HOP_LIMIT_MASK;
p->want_ack = !!(e->header.flags & PACKET_FLAGS_WANT_ACK_MASK);
p->via_mqtt = !!(e->header.flags & PACKET_FLAGS_VIA_MQTT_MASK);
p->hop_start = (e->header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
p->which_payload_variant = e->encrypted ? meshtastic_MeshPacket_encrypted_tag : meshtastic_MeshPacket_decoded_tag;
unsigned char *payload = ((unsigned char *)e) + sizeof(PacketCacheEntry);
PacketCacheMetadata m{};
if (e->has_metadata) {
memcpy(&m, (payload + e->payload_len), sizeof(m));
p->rx_rssi = ((int)m.rx_rssi) - 200;
p->rx_snr = ((float)m.rx_snr * 0.25f) - 30.0f;
p->rx_time = m.rx_time;
p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)m.transport_mechanism;
p->priority = (meshtastic_MeshPacket_Priority)m.priority;
}
if (e->encrypted) {
memcpy(p->encrypted.bytes, payload, e->payload_len);
p->encrypted.size = e->payload_len;
} else {
memcpy(p->decoded.payload.bytes, payload, e->payload_len);
p->decoded.payload.size = e->payload_len;
if (e->has_metadata) {
// Decrypted-only metadata
p->decoded.portnum = (meshtastic_PortNum)m.portnum;
p->decoded.want_response = m.want_response;
p->decoded.emoji = m.emoji;
p->decoded.bitfield = m.bitfield;
if (m.reply_id)
p->decoded.reply_id = m.reply_id;
else if (m.request_id)
p->decoded.request_id = m.request_id;
}
}
}
/**
* Release a cache entry
*/
void PacketCache::release(PacketCacheEntry *e)
{
if (!e)
return;
remove(e);
size -= sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0);
free(e);
}
/**
* Insert a new entry into the hash table
*/
void PacketCache::insert(PacketCacheEntry *e)
{
assert(e);
PacketHash h = PACKET_HASH(e->header.from, e->header.id);
PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)];
e->next = *target;
*target = e;
num_entries++;
}
/**
* Remove an entry from the hash table
*/
void PacketCache::remove(PacketCacheEntry *e)
{
assert(e);
PacketHash h = PACKET_HASH(e->header.from, e->header.id);
PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)];
while (*target) {
if (*target == e) {
*target = e->next;
e->next = NULL;
num_entries--;
break;
} else {
target = &(*target)->next;
}
}
}

View File

@@ -1,75 +0,0 @@
#pragma once
#include "RadioInterface.h"
#define PACKET_HASH(a, b) ((((a ^ b) >> 16) ^ (a ^ b)) & 0xFFFF) // 16 bit fold of packet (from, id) tuple
typedef uint16_t PacketHash;
#define PACKET_CACHE_BUCKETS 64 // Number of hash table buckets
#define PACKET_CACHE_BUCKET(h) (((h >> 12) ^ (h >> 6) ^ h) & 0x3F) // Fold hash down to 6-bit bucket index
typedef struct PacketCacheEntry {
PacketCacheEntry *next;
PacketHeader header;
uint16_t payload_len = 0;
union {
uint16_t bitfield;
struct {
uint8_t encrypted : 1; // Payload is encrypted
uint8_t has_metadata : 1; // Payload includes PacketCacheMetadata
uint8_t : 6; // Reserved for future use
uint16_t : 8; // Reserved for future use
};
};
} PacketCacheEntry;
typedef struct PacketCacheMetadata {
PacketCacheMetadata() : _bitfield(0), reply_id(0), _bitfield2(0) {}
union {
uint32_t _bitfield;
struct {
uint16_t portnum : 9; // meshtastic_MeshPacket::decoded::portnum
uint16_t want_response : 1; // meshtastic_MeshPacket::decoded::want_response
uint16_t emoji : 1; // meshtastic_MeshPacket::decoded::emoji
uint16_t bitfield : 5; // meshtastic_MeshPacket::decoded::bitfield (truncated)
uint8_t rx_rssi : 8; // meshtastic_MeshPacket::rx_rssi (map via actual RSSI + 200)
uint8_t rx_snr : 8; // meshtastic_MeshPacket::rx_snr (map via (p->rx_snr + 30.0f) / 0.25f)
};
};
union {
uint32_t reply_id; // meshtastic_MeshPacket::decoded.reply_id
uint32_t request_id; // meshtastic_MeshPacket::decoded.request_id
};
uint32_t rx_time = 0; // meshtastic_MeshPacket::rx_time
uint8_t transport_mechanism = 0; // meshtastic_MeshPacket::transport_mechanism
struct {
uint8_t _bitfield2;
union {
uint8_t priority : 7; // meshtastic_MeshPacket::priority
uint8_t reserved : 1; // Reserved for future use
};
};
} PacketCacheMetadata;
class PacketCache
{
public:
PacketCacheEntry *cache(const meshtastic_MeshPacket *p, bool preserveMetadata);
static void dump(void *dest, const PacketCacheEntry **entries, size_t num_entries);
size_t dumpSize(const PacketCacheEntry **entries, size_t num_entries);
PacketCacheEntry *find(NodeNum from, PacketId id);
PacketCacheEntry *find(PacketHash h);
bool load(void *src, PacketCacheEntry **entries, size_t num_entries);
size_t getNumEntries() { return num_entries; }
size_t getSize() { return size; }
void rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p);
void release(PacketCacheEntry *e);
private:
PacketCacheEntry *buckets[PACKET_CACHE_BUCKETS]{};
size_t num_entries = 0;
size_t size = 0;
void insert(PacketCacheEntry *e);
void remove(PacketCacheEntry *e);
};
extern PacketCache packetCache;

View File

@@ -67,14 +67,8 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
r.id = p->id;
r.sender = getFrom(p); // If 0 then use our ID
r.next_hop = p->next_hop;
setHighestHopLimit(r, p->hop_limit);
bool weWillRelay = false;
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum());
if (p->relay_node == ourRelayID) { // If the relay_node is us, store it
weWillRelay = true;
setOurTxHopLimit(r, p->hop_limit);
r.hop_limit = p->hop_limit;
r.relayed_by[0] = p->relay_node;
}
r.rxTimeMsec = millis(); //
if (r.rxTimeMsec == 0) // =0 every 49.7 days? 0 is special
@@ -100,6 +94,8 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
}
if (seenRecently) {
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum()); // Get our relay ID from our node number
if (wasFallback) {
// If it was seen with a next-hop not set to us and now it's NO_NEXT_HOP_PREFERENCE, and the relayer relayed already
// before, it's a fallback to flooding. If we didn't already relay and the next-hop neither, we might need to handle
@@ -141,40 +137,16 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
found->sender, found->id, found->next_hop, found->relayed_by[0], found->relayed_by[1], found->relayed_by[2],
millis() - found->rxTimeMsec);
#endif
// Only update the relayer if it heard us directly (meaning hopLimit is decreased by 1)
uint8_t startIdx = weWillRelay ? 1 : 0;
if (!weWillRelay) {
bool weWereRelayer = wasRelayer(ourRelayID, *found);
// We were a relayer and the packet came in with a hop limit that is one less than when we sent it out
if (weWereRelayer && (p->hop_limit == getOurTxHopLimit(*found) || p->hop_limit == getOurTxHopLimit(*found) - 1)) {
r.relayed_by[0] = p->relay_node;
startIdx = 1; // Start copying existing relayers from index 1
}
// keep the original ourTxHopLimit
setOurTxHopLimit(r, getOurTxHopLimit(*found));
}
// Preserve the highest hop_limit we've ever seen for this packet so upgrades aren't lost when a later copy has
// fewer hops remaining.
if (getHighestHopLimit(*found) > getHighestHopLimit(r))
setHighestHopLimit(r, getHighestHopLimit(*found));
if (found->hop_limit > r.hop_limit)
r.hop_limit = found->hop_limit;
// Add the existing relayed_by to the new record, avoiding duplicates
for (uint8_t i = 0; i < (NUM_RELAYERS - startIdx); i++) {
if (found->relayed_by[i] == 0)
continue;
bool exists = false;
for (uint8_t j = 0; j < NUM_RELAYERS; j++) {
if (r.relayed_by[j] == found->relayed_by[i]) {
exists = true;
break;
}
}
if (!exists) {
r.relayed_by[i + startIdx] = found->relayed_by[i];
}
// Add the existing relayed_by to the new record
for (uint8_t i = 0; i < (NUM_RELAYERS - 1); i++) {
if (found->relayed_by[i] != 0)
r.relayed_by[i + 1] = found->relayed_by[i];
}
r.next_hop = found->next_hop; // keep the original next_hop (such that we check whether we were originally asked)
#if VERBOSE_PACKET_HISTORY
@@ -397,6 +369,14 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r, boo
return found;
}
// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
bool PacketHistory::wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
bool wasSole = false;
wasRelayer(relayer, id, sender, &wasSole);
return wasSole;
}
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
@@ -438,24 +418,3 @@ void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, cons
found->id, found->relayed_by[0], found->relayed_by[1], found->relayed_by[2], relayer, i != j);
#endif
}
// Getters and setters for hop limit fields packed in hop_limit
inline uint8_t PacketHistory::getHighestHopLimit(PacketRecord &r)
{
return r.hop_limit & HOP_LIMIT_HIGHEST_MASK;
}
inline void PacketHistory::setHighestHopLimit(PacketRecord &r, uint8_t hopLimit)
{
r.hop_limit = (r.hop_limit & ~HOP_LIMIT_HIGHEST_MASK) | (hopLimit & HOP_LIMIT_HIGHEST_MASK);
}
inline uint8_t PacketHistory::getOurTxHopLimit(PacketRecord &r)
{
return (r.hop_limit & HOP_LIMIT_OUR_TX_MASK) >> HOP_LIMIT_OUR_TX_SHIFT;
}
inline void PacketHistory::setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit)
{
r.hop_limit = (r.hop_limit & ~HOP_LIMIT_OUR_TX_MASK) | ((hopLimit << HOP_LIMIT_OUR_TX_SHIFT) & HOP_LIMIT_OUR_TX_MASK);
}

View File

@@ -2,11 +2,8 @@
#include "NodeDB.h"
// Number of relayers we keep track of. Use 6 to be efficient with memory alignment of PacketRecord to 20 bytes
#define NUM_RELAYERS 6
#define HOP_LIMIT_HIGHEST_MASK 0x07 // Bits 0-2
#define HOP_LIMIT_OUR_TX_MASK 0x38 // Bits 3-5
#define HOP_LIMIT_OUR_TX_SHIFT 3 // Bits 3-5
#define NUM_RELAYERS \
3 // Number of relayer we keep track of. Use 3 to be efficient with memory alignment of PacketRecord to 16 bytes
/**
* This is a mixin that adds a record of past packets we have seen
@@ -19,10 +16,9 @@ class PacketHistory
PacketId id;
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it, 0 means empty
uint8_t next_hop; // The next hop asked for this packet
uint8_t hop_limit; // bit 0-2: Highest hop limit observed for this packet,
// bit 3-5: our hop limit when we first transmitted it
uint8_t hop_limit; // Highest hop limit observed for this packet
uint8_t relayed_by[NUM_RELAYERS]; // Array of nodes that relayed this packet
}; // 4B + 4B + 4B + 1B + 1B + 6B = 20B
}; // 4B + 4B + 4B + 1B + 1B + 3B = 17B (will be padded to 20B)
uint32_t recentPacketsCapacity =
0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets.
@@ -43,11 +39,6 @@ class PacketHistory
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole = nullptr);
uint8_t getHighestHopLimit(PacketRecord &r);
void setHighestHopLimit(PacketRecord &r, uint8_t hopLimit);
uint8_t getOurTxHopLimit(PacketRecord &r);
void setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit);
PacketHistory(const PacketHistory &); // non construction-copyable
PacketHistory &operator=(const PacketHistory &); // non copyable
public:
@@ -70,6 +61,9 @@ class PacketHistory
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole = nullptr);
// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
bool wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);

View File

@@ -72,7 +72,6 @@ void PhoneAPI::handleStartConfig()
LOG_INFO("Start API client config");
nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos
nodeInfoQueue.clear();
resetReadIndex();
}
@@ -95,7 +94,6 @@ void PhoneAPI::close()
fromRadioScratch = {};
toRadioScratch = {};
nodeInfoForPhone = {};
nodeInfoQueue.clear();
packetForPhone = NULL;
filesManifest.clear();
fromRadioNum = 0;
@@ -434,24 +432,15 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
case STATE_SEND_OTHER_NODEINFOS: {
LOG_DEBUG("Send known nodes");
if (nodeInfoForPhone.num == 0 && !nodeInfoQueue.empty()) {
// Serve the next cached node without re-reading from the DB iterator.
nodeInfoForPhone = nodeInfoQueue.front();
nodeInfoQueue.pop_front();
}
if (nodeInfoForPhone.num != 0) {
// Just in case we stored a different user.id in the past, but should never happen going forward
sprintf(nodeInfoForPhone.user.id, "!%08x", nodeInfoForPhone.num);
LOG_DEBUG("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
fromRadioScratch.node_info = nodeInfoForPhone;
nodeInfoForPhone = {};
prefetchNodeInfos();
// Stay in current state until done sending nodeinfos
nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time
} else {
LOG_DEBUG("Done sending nodeinfo");
nodeInfoQueue.clear();
state = STATE_SEND_FILEMANIFEST;
// Go ahead and send that ID right now
return getFromRadio(buf);
@@ -555,30 +544,6 @@ void PhoneAPI::releaseQueueStatusPhonePacket()
}
}
void PhoneAPI::prefetchNodeInfos()
{
bool added = false;
// Keep the queue topped up so BLE reads stay responsive even if DB fetches take a moment.
while (nodeInfoQueue.size() < kNodePrefetchDepth) {
auto nextNode = nodeDB->readNextMeshNode(readIndex);
if (!nextNode)
break;
auto info = TypeConversions::ConvertToNodeInfo(nextNode);
bool isUs = info.num == nodeDB->getNodeNum();
info.hops_away = isUs ? 0 : info.hops_away;
info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
info.snr = isUs ? 0 : info.snr;
info.via_mqtt = isUs ? false : info.via_mqtt;
info.is_favorite = info.is_favorite || isUs;
nodeInfoQueue.push_back(info);
added = true;
}
if (added)
onNowHasData(0);
}
void PhoneAPI::releaseMqttClientProxyPhonePacket()
{
if (mqttClientProxyMessageForPhone) {
@@ -615,8 +580,19 @@ bool PhoneAPI::available()
return true;
case STATE_SEND_OTHER_NODEINFOS:
if (nodeInfoQueue.empty())
prefetchNodeInfos();
if (nodeInfoForPhone.num == 0) {
auto nextNode = nodeDB->readNextMeshNode(readIndex);
if (nextNode) {
nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode);
bool isUs = nodeInfoForPhone.num == nodeDB->getNodeNum();
nodeInfoForPhone.hops_away = isUs ? 0 : nodeInfoForPhone.hops_away;
nodeInfoForPhone.last_heard = isUs ? getValidTime(RTCQualityFromNet) : nodeInfoForPhone.last_heard;
nodeInfoForPhone.snr = isUs ? 0 : nodeInfoForPhone.snr;
nodeInfoForPhone.via_mqtt = isUs ? false : nodeInfoForPhone.via_mqtt;
nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || isUs; // Our node is always a favorite
onNowHasData(0);
}
}
return true; // Always say we have something, because we might need to advance our state machine
case STATE_SEND_PACKETS: {
if (!queueStatusPacketForPhone)
@@ -734,13 +710,6 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
// sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Text messages can only be sent once every 2 seconds");
return false;
}
// Upgrade traceroute requests from phone to use reliable delivery, matching TraceRouteModule
if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && !isBroadcast(p.to)) {
// Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)
p.want_ack = true;
}
lastPortNumToRadio[p.decoded.portnum] = millis();
service->handleToRadio(p);
return true;
@@ -756,7 +725,7 @@ int PhoneAPI::onNotify(uint32_t newValue)
LOG_INFO("Tell client we have new packets %u", newValue);
onNowHasData(newValue);
} else {
LOG_DEBUG("Client not yet interested in packets (state=%d)", state);
LOG_DEBUG("(Client not yet interested in packets)");
}
return timeout ? -1 : 0; // If we timed out, MeshService should stop iterating through observers as we just removed one

View File

@@ -3,7 +3,6 @@
#include "Observer.h"
#include "mesh-pb-constants.h"
#include "meshtastic/portnums.pb.h"
#include <deque>
#include <iterator>
#include <string>
#include <unordered_map>
@@ -80,10 +79,6 @@ class PhoneAPI
/// We temporarily keep the nodeInfo here between the call to available and getFromRadio
meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default;
// Prefetched node info entries ready for immediate transmission to the phone.
std::deque<meshtastic_NodeInfo> nodeInfoQueue;
// Tunable size of the node info cache so we can keep BLE reads non-blocking.
static constexpr size_t kNodePrefetchDepth = 4;
meshtastic_ToRadio toRadioScratch = {
0}; // this is a static scratch object, any data must be copied elsewhere before returning
@@ -163,8 +158,6 @@ class PhoneAPI
void releaseQueueStatusPhonePacket();
void prefetchNodeInfos();
void releaseMqttClientProxyPhonePacket();
void releaseClientNotification();

View File

@@ -248,6 +248,9 @@ bool RF95Interface::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
// Initialize AGC reset timing
nextAgcResetMs = millis() + 30000;
startReceive(); // restart receiving
return RADIOLIB_ERR_NONE;

View File

@@ -65,8 +65,6 @@ class RF95Interface : public RadioLibInterface
*/
virtual void configHardwareForSend() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(*lora, pl, received); }
private:
/** Some boards require GPIO control of tx vs rx paths */
void setTransmitEnable(bool txon);

View File

@@ -230,7 +230,33 @@ The band is from 902 to 928 MHz. It mentions channel number and its respective c
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
*/
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received)
/**
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
* section 4
*
* @return num msecs for the packet
*/
uint32_t RadioInterface::getPacketTime(uint32_t pl)
{
float bandwidthHz = bw * 1000.0f;
bool headDisable = false; // we currently always use the header
float tSym = (1 << sf) / bandwidthHz;
bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
float tPreamble = (preambleLength + 4.25f) * tSym;
float numPayloadSym =
8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
float tPayload = numPayloadSym * tSym;
float tPacket = tPreamble + tPayload;
uint32_t msecs = tPacket * 1000;
return msecs;
}
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p)
{
uint32_t pl = 0;
if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
@@ -239,7 +265,7 @@ uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool rece
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
pl = numbytes + sizeof(PacketHeader);
}
return getPacketTime(pl, received);
return getPacketTime(pl);
}
/** The delay to use for retransmitting dropped packets */
@@ -291,8 +317,9 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
/** Returns true if we should rebroadcast early like a ROUTER */
bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
{
// If we are a ROUTER, we always rebroadcast early
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
// 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;
}
@@ -598,7 +625,8 @@ void RadioInterface::applyModemConfig()
saveFreq(freq + loraConfig.frequency_offset);
slotTimeMsec = computeSlotTimeMsec();
preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
preambleTimeMsec = getPacketTime((uint32_t)0);
maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset);
LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset,
@@ -608,7 +636,7 @@ void RadioInterface::applyModemConfig()
LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw);
LOG_INFO("channel_num: %d", channel_num + 1);
LOG_INFO("frequency: %f", getFreq());
LOG_INFO("Slot time: %u msec, preamble time: %u msec", slotTimeMsec, preambleTimeMsec);
LOG_INFO("Slot time: %u msec", slotTimeMsec);
}
/** Slottime is the time to detect a transmission has started, consisting of:
@@ -646,26 +674,11 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
power = maxPower;
}
#ifndef NUM_PA_POINTS
if (TX_GAIN_LORA > 0 && !devicestate.owner.is_licensed) {
if (TX_GAIN_LORA > 0) {
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA);
power -= TX_GAIN_LORA;
}
#else
if (!devicestate.owner.is_licensed) {
// we have an array of PA gain values. Find the highest power setting that works.
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
// we've exceeded the power limit, or hit the max we can do
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
power -= tx_gain[radio_dbm];
break;
}
}
}
#endif
if (power > loraMaxPower) // Clamp power to maximum defined level
power = loraMaxPower;

View File

@@ -89,6 +89,7 @@ class RadioInterface
uint32_t slotTimeMsec = computeSlotTimeMsec();
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast
const uint32_t PROCESSING_TIME_MSEC =
4500; // time to construct, process and construct a packet again (empirically determined)
const uint8_t CWmin = 3; // minimum CWsize
@@ -201,8 +202,8 @@ class RadioInterface
*
* @return num msecs for the packet
*/
uint32_t getPacketTime(const meshtastic_MeshPacket *p, bool received = false);
virtual uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) = 0;
uint32_t getPacketTime(const meshtastic_MeshPacket *p);
uint32_t getPacketTime(uint32_t totalPacketLen);
/**
* Get the channel we saved.

View File

@@ -37,7 +37,7 @@ void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in)
RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy, PhysicalLayer *_iface)
: NotifiedWorkerThread("RadioIf"), module(hal, cs, irq, rst, busy), iface(_iface)
: NotifiedWorkerThread("RadioIf"), module(hal, cs, irq, rst, busy), iface(_iface), nextAgcResetMs(0)
{
instance = this;
#if defined(ARCH_STM32WL) && defined(USE_SX1262)
@@ -116,23 +116,18 @@ bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidF
if (detected) {
if (!activeReceiveStart) {
activeReceiveStart = millis();
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec)) {
if (!(irq & syncWordHeaderValidFlag)) {
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && !(irq & syncWordHeaderValidFlag)) {
// The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
activeReceiveStart = 0;
LOG_DEBUG("Ignore false preamble detection");
return false;
} else {
uint32_t maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
// We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
activeReceiveStart = 0;
LOG_DEBUG("Ignore false header detection");
return false;
}
}
}
}
return detected;
}
@@ -177,12 +172,7 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p)
printPacket("enqueue for send", p);
LOG_DEBUG("txGood=%d,txRelay=%d,rxGood=%d,rxBad=%d", txGood, txRelay, rxGood, rxBad);
bool dropped = false;
ErrorCode res = txQueue.enqueue(p, &dropped) ? ERRNO_OK : ERRNO_UNKNOWN;
if (dropped) {
txDrop++;
}
ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN;
if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
packetPool.release(p);
@@ -255,6 +245,9 @@ currently active.
*/
void RadioLibInterface::onNotify(uint32_t notification)
{
// Check for AGC reset before processing notifications
checkAndPerformAgcReset();
switch (notification) {
case ISR_TX:
handleTransmitInterrupt();
@@ -364,15 +357,11 @@ void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id)
meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false);
if (p) {
p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr);
bool dropped = false;
if (txQueue.enqueue(p, &dropped)) {
if (txQueue.enqueue(p)) {
LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
} else {
packetPool.release(p);
}
if (dropped) {
txDrop++;
}
}
}
@@ -425,6 +414,8 @@ void RadioLibInterface::completeSending()
void RadioLibInterface::handleReceiveInterrupt()
{
uint32_t xmitMsec;
// when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race
// Condition?
if (!isReceiving) {
@@ -437,12 +428,12 @@ void RadioLibInterface::handleReceiveInterrupt()
// read the number of actually received bytes
size_t length = iface->getPacketLength();
uint32_t rxMsec = getPacketTime(length, true);
xmitMsec = getPacketTime(length);
#ifndef DISABLE_WELCOME_UNSET
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
LOG_WARN("lora rx disabled: Region unset");
airTime->logAirtime(RX_ALL_LOG, rxMsec);
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
return;
}
#endif
@@ -458,7 +449,7 @@ void RadioLibInterface::handleReceiveInterrupt()
radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
rxBad++;
airTime->logAirtime(RX_ALL_LOG, rxMsec);
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
} else {
// Skip the 4 headers that are at the beginning of the rxBuf
@@ -468,7 +459,7 @@ void RadioLibInterface::handleReceiveInterrupt()
if (payloadLen < 0) {
LOG_WARN("Ignore received packet too short");
rxBad++;
airTime->logAirtime(RX_ALL_LOG, rxMsec);
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
} else {
rxGood++;
// altered packet with "from == 0" can do Remote Node Administration without permission
@@ -506,7 +497,7 @@ void RadioLibInterface::handleReceiveInterrupt()
printPacket("Lora RX", mp);
airTime->logAirtime(RX_LOG, rxMsec);
airTime->logAirtime(RX_LOG, xmitMsec);
deliverToReceiver(mp);
}
@@ -565,3 +556,45 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
return res == RADIOLIB_ERR_NONE;
}
}
void RadioLibInterface::checkAndPerformAgcReset()
{
// Use a sensible default of 30 seconds for AGC/AFC reset
// Based on MeshCore's approach and SX126x datasheet recommendations
static const uint32_t AGC_RESET_INTERVAL_MS = 30 * 1000U;
uint32_t now = millis();
// Add debug info on first call or when nextAgcResetMs is not set
static bool first_call = true;
if (first_call || nextAgcResetMs == 0) {
LOG_DEBUG("AGC reset: now=%u, nextAgcResetMs=%u, first_call=%d", now, nextAgcResetMs, first_call);
first_call = false;
if (nextAgcResetMs == 0) {
// Initialize if not set by reconfigure()
nextAgcResetMs = now + AGC_RESET_INTERVAL_MS;
LOG_DEBUG("AGC reset initialized to %u", nextAgcResetMs);
}
}
if (now < nextAgcResetMs) {
return; // Not time yet
}
// Only reset if we're not actively sending, receiving, or processing packets
if (isSending() || isActivelyReceiving() || isReceiving) {
// Postpone reset by a short delay to avoid interfering with active operations
nextAgcResetMs = now + 1000; // Retry in 1 second
LOG_DEBUG("AGC reset postponed - radio busy");
return;
}
LOG_INFO("Performing AGC/AFC reset via startReceive()");
// Use MeshCore's approach: issue startReceive() to reset AGC/AFC
// This is cleaner than direct register manipulation and works across all radio types
startReceive();
// Schedule next reset
nextAgcResetMs = now + AGC_RESET_INTERVAL_MS;
}

View File

@@ -16,6 +16,8 @@
#define RADIOLIB_PIN_TYPE uint32_t
#define THIRY_SECONDS_MS 30000
// In addition to the default Rx flags, we need the PREAMBLE_DETECTED flag to detect whether we are actively receiving
#define MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS (RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1 << RADIOLIB_IRQ_PREAMBLE_DETECTED))
@@ -61,17 +63,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE);
protected:
ModemType_t modemType = RADIOLIB_MODEM_LORA;
DataRate_t getDataRate() const { return {.lora = {.spreadingFactor = sf, .bandwidth = bw, .codingRate = cr}}; }
PacketConfig_t getPacketConfig() const
{
return {.lora = {.preambleLength = preambleLength,
.implicitHeader = false,
.crcEnabled = true,
// We use auto LDRO, meaning it is enabled if the symbol time is >= 16msec
.ldrOptimize = (1 << sf) / bw >= 16}};
}
/**
* We use a meshtastic sync word, but hashed with the Channel name. For releases before 1.2 we used 0x12 (or for very old
* loads 0x14) Note: do not use 0x34 - that is reserved for lorawan
@@ -116,7 +107,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
* Debugging counts
*/
uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0;
uint16_t txDrop = 0;
public:
RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
@@ -193,8 +183,18 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
protected:
uint32_t activeReceiveStart = 0;
/** Track when we should next perform an AGC/AFC reset */
uint32_t nextAgcResetMs = 0;
bool receiveDetected(uint16_t irq, ulong syncWordHeaderValidFlag, ulong preambleDetectedFlag);
/**
* Perform AGC/AFC reset by issuing a startReceive() call if enough time has passed
* and we're not currently receiving or transmitting.
* Based on MeshCore's approach of using RadioLib startReceive() to reset AGC.
*/
void checkAndPerformAgcReset();
/** Do any hardware setup needed on entry into send configuration for the radio.
* Subclasses can customize, but must also call this base method */
virtual void configHardwareForSend();
@@ -221,36 +221,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
*/
virtual void setStandby();
/**
* Derive packet time either for a received (using header info) or a transmitted packet
*/
template <typename T> uint32_t computePacketTime(T &lora, uint32_t pl, bool received)
{
if (received) {
// First get the actual coding rate and CRC status from the received packet
uint8_t rxCR;
bool hasCRC;
lora.getLoRaRxHeaderInfo(&rxCR, &hasCRC);
// Go from raw header value to denominator
if (rxCR < 5) {
rxCR += 4;
} else if (rxCR == 7) {
rxCR = 8;
}
// Received packet configuration must be the same as configured, except for coding rate and CRC
DataRate_t dr = getDataRate();
dr.lora.codingRate = rxCR;
PacketConfig_t pc = getPacketConfig();
pc.lora.crcEnabled = hasCRC;
return lora.calculateTimeOnAir(modemType, dr, pc, pl) / 1000;
}
return lora.getTimeOnAir(pl) / 1000;
}
const char *radioLibErr = "RadioLib err=";
/**

View File

@@ -76,7 +76,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
If we don't add this, we will likely retransmit too early.
*/
for (auto i = pending.begin(); i != pending.end(); i++) {
i->second.nextTxMsec += iface->getPacketTime(p, true);
i->second.nextTxMsec += iface->getPacketTime(p);
}
return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p);
@@ -103,20 +103,10 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
/* 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 (shouldSuccessAckWithWantAck(p)) {
// If this packet should always be ACKed reliably with want_ack back to the original sender, make sure we
// do that unconditionally.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit), true);
} else if (!p->decoded.request_id && !p->decoded.reply_id) {
// If it's not an ACK or a reply, send an ACK.
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) {
// If we received the packet directly from the original sender, send a 0-hop ACK since the original sender
// won't overhear any implicit ACKs. If we received the packet via NextHopRouter, also send a 0-hop ACK to
// stop the immediate relayer's retransmissions.
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 &&
@@ -163,35 +153,3 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
// handle the packet as normal
isBroadcast(p->to) ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c);
}
/**
* If we ACK this packet, should we set want_ack=true on the ACK for reliable delivery of the ACK packet?
*/
bool ReliableRouter::shouldSuccessAckWithWantAck(const meshtastic_MeshPacket *p)
{
// Don't ACK-with-want-ACK outgoing packets
if (isFromUs(p))
return false;
// Only ACK-with-want-ACK if the original packet asked for want_ack
if (!p->want_ack)
return false;
// Only ACK-with-want-ACK packets to us (not broadcast)
if (!isToUs(p))
return false;
// Special case for text message DMs:
bool isTextMessage =
(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_TEXT_MESSAGE_APP, meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP);
if (isTextMessage) {
// If it's a non-broadcast text message, and the original asked for want_ack,
// let's send an ACK that is itself want_ack to improve reliability of confirming delivery back to the sender.
// This should include all DMs regardless of whether or not reply_id is set.
return true;
}
return false;
}

View File

@@ -31,10 +31,4 @@ class ReliableRouter : public NextHopRouter
* We hook this method so we can see packets before FloodingRouter says they should be discarded
*/
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
private:
/**
* Should this packet be ACKed with a want_ack for reliable delivery?
*/
bool shouldSuccessAckWithWantAck(const meshtastic_MeshPacket *p);
};

View File

@@ -90,7 +90,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
// For subsequent hops, check if previous relay is a favorite router
// Optimized search for favorite routers with matching last byte
// Check ordering optimized for IoT devices (cheapest checks first)
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
for (int i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
if (!node)
continue;
@@ -105,7 +105,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
// Check 3: role check (moderate cost - multiple comparisons)
if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
continue;
}
@@ -198,10 +198,9 @@ meshtastic_MeshPacket *Router::allocForSending()
/**
* Send an ack or a nak packet back towards whoever sent idFrom
*/
void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit,
bool ackWantsAck)
void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit)
{
routingModule->sendAckNak(err, to, idFrom, chIndex, hopLimit, ackWantsAck);
routingModule->sendAckNak(err, to, idFrom, chIndex, hopLimit);
}
void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p)
@@ -400,6 +399,10 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
{
concurrency::LockGuard g(cryptLock);
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_ALL_SKIP_DECODING)
return DecodeState::DECODE_FAILURE;
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
(nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) {
LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet", p->from);
@@ -480,6 +483,35 @@ 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

View File

@@ -125,8 +125,7 @@ class Router : protected concurrency::OSThread, protected PacketHistory
/**
* Send an ack or a nak packet back towards whoever sent idFrom
*/
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
bool ackWantsAck = false);
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0);
private:
/**

View File

@@ -52,7 +52,7 @@ template <typename T> bool SX126xInterface<T>::init()
pinMode(SX126X_POWER_EN, OUTPUT);
#endif
#if defined(USE_GC1109_PA)
#ifdef HELTEC_V4
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
@@ -80,9 +80,6 @@ template <typename T> bool SX126xInterface<T>::init()
RadioLibInterface::init();
limitPower(SX126X_MAX_POWER);
// Make sure we reach the minimum power supported to turn the chip on (-9dBm)
if (power < -9)
power = -9;
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
// \todo Display actual typename of the adapter, not just `SX126x`
@@ -231,6 +228,9 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
LOG_ERROR("SX126X setOutputPower %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
// Initialize AGC/AFC reset timing (30 second interval)
nextAgcResetMs = millis() + THIRY_SECONDS_MS;
startReceive(); // restart receiving
return RADIOLIB_ERR_NONE;
@@ -266,7 +266,6 @@ template <typename T> void SX126xInterface<T>::addReceiveMetadata(meshtastic_Mes
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
}
/** We override to turn on transmitter power as needed.
@@ -353,7 +352,7 @@ template <typename T> bool SX126xInterface<T>::sleep()
digitalWrite(SX126X_POWER_EN, LOW);
#endif
#if defined(USE_GC1109_PA)
#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.
@@ -368,7 +367,7 @@ template <typename T> bool SX126xInterface<T>::sleep()
/** Some boards require GPIO control of tx vs rx paths */
template <typename T> void SX126xInterface<T>::setTransmitEnable(bool txon)
{
#if defined(USE_GC1109_PA)
#ifdef HELTEC_V4
digitalWrite(LORA_PA_POWER, HIGH);
digitalWrite(LORA_PA_EN, HIGH);
digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);

View File

@@ -72,8 +72,6 @@ template <class T> class SX126xInterface : public RadioLibInterface
virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
private:
/** Some boards require GPIO control of tx vs rx paths */
void setTransmitEnable(bool txon);

View File

@@ -150,6 +150,9 @@ template <typename T> bool SX128xInterface<T>::reconfigure()
LOG_ERROR("SX128X setOutputPower %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
// Initialize AGC/AFC reset timing (30 second interval)
nextAgcResetMs = millis() + THIRY_SECONDS_MS;
startReceive(); // restart receiving
return RADIOLIB_ERR_NONE;
@@ -204,7 +207,6 @@ template <typename T> void SX128xInterface<T>::addReceiveMetadata(meshtastic_Mes
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
}
/** We override to turn on transmitter power as needed.

View File

@@ -67,6 +67,4 @@ template <class T> class SX128xInterface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
};

View File

@@ -132,8 +132,6 @@ typedef struct _meshtastic_SharedContact {
meshtastic_User user;
/* Add this contact to the blocked / ignored list */
bool should_ignore;
/* Set the IS_KEY_MANUALLY_VERIFIED bit */
bool manually_verified;
} meshtastic_SharedContact;
/* This message is used by a client to initiate or complete a key verification */
@@ -321,13 +319,13 @@ extern "C" {
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
#define meshtastic_HamParameters_init_default {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0}
#define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
#define meshtastic_HamParameters_init_zero {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0}
#define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
/* Field tags (for use in manual encoding/decoding) */
@@ -343,7 +341,6 @@ extern "C" {
#define meshtastic_SharedContact_node_num_tag 1
#define meshtastic_SharedContact_user_tag 2
#define meshtastic_SharedContact_should_ignore_tag 3
#define meshtastic_SharedContact_manually_verified_tag 4
#define meshtastic_KeyVerificationAdmin_message_type_tag 1
#define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2
#define meshtastic_KeyVerificationAdmin_nonce_tag 3
@@ -507,8 +504,7 @@ X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 1)
#define meshtastic_SharedContact_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, node_num, 1) \
X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \
X(a, STATIC, SINGULAR, BOOL, should_ignore, 3) \
X(a, STATIC, SINGULAR, BOOL, manually_verified, 4)
X(a, STATIC, SINGULAR, BOOL, should_ignore, 3)
#define meshtastic_SharedContact_CALLBACK NULL
#define meshtastic_SharedContact_DEFAULT NULL
#define meshtastic_SharedContact_user_MSGTYPE meshtastic_User
@@ -543,7 +539,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
#define meshtastic_HamParameters_size 31
#define meshtastic_KeyVerificationAdmin_size 25
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
#define meshtastic_SharedContact_size 127
#define meshtastic_SharedContact_size 125
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -34,9 +34,9 @@ typedef enum _meshtastic_Channel_Role {
typedef struct _meshtastic_ModuleSettings {
/* Bits of precision for the location sent in position packets. */
uint32_t position_precision;
/* Controls whether or not the client / device should mute the current channel
/* Controls whether or not the phone / clients should mute the current channel
Useful for noisy public channels you don't necessarily want to disable */
bool is_muted;
bool is_client_muted;
} meshtastic_ModuleSettings;
typedef PB_BYTES_ARRAY_T(32) meshtastic_ChannelSettings_psk_t;
@@ -137,7 +137,7 @@ extern "C" {
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_ModuleSettings_position_precision_tag 1
#define meshtastic_ModuleSettings_is_muted_tag 2
#define meshtastic_ModuleSettings_is_client_muted_tag 2
#define meshtastic_ChannelSettings_channel_num_tag 1
#define meshtastic_ChannelSettings_psk_tag 2
#define meshtastic_ChannelSettings_name_tag 3
@@ -164,7 +164,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7)
#define meshtastic_ModuleSettings_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, position_precision, 1) \
X(a, STATIC, SINGULAR, BOOL, is_muted, 2)
X(a, STATIC, SINGULAR, BOOL, is_client_muted, 2)
#define meshtastic_ModuleSettings_CALLBACK NULL
#define meshtastic_ModuleSettings_DEFAULT NULL

View File

@@ -26,8 +26,7 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT = 3,
/* Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
Deprecated in v2.7.11 because it creates "holes" in the mesh rebroadcast chain. */
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate. */
meshtastic_Config_DeviceConfig_Role_REPEATER = 4,
/* Description: Broadcasts GPS position packets as priority.
Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default.

View File

@@ -68,8 +68,6 @@ typedef enum _meshtastic_Language {
meshtastic_Language_BULGARIAN = 17,
/* Czech */
meshtastic_Language_CZECH = 18,
/* Danish */
meshtastic_Language_DANISH = 19,
/* Simplified Chinese (experimental) */
meshtastic_Language_SIMPLIFIED_CHINESE = 30,
/* Traditional Chinese (experimental) */

View File

@@ -253,8 +253,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 = 99,
/* Seeed Tracker L1 EINK driver */
meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK = 100,
/* Muzi Works R1 Neo */
meshtastic_HardwareModel_MUZI_R1_NEO = 101,
/* Reserved ID for future and past use */
meshtastic_HardwareModel_QWANTZ_TINY_ARMS = 101,
/* Lilygo T-Deck Pro */
meshtastic_HardwareModel_T_DECK_PRO = 102,
/* Lilygo TLora Pager */
@@ -278,10 +278,6 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_M5STACK_C6L = 111,
/* M5Stack Cardputer Adv */
meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
/* ESP32S3 main controller with GPS and TFT screen. */
meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 = 113,
/* LilyGo T-Watch Ultra */
meshtastic_HardwareModel_T_WATCH_ULTRA = 114,
/* ------------------------------------------------------------------------------------------------------------------------------------------
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

@@ -357,8 +357,6 @@ typedef struct _meshtastic_LocalStats {
uint32_t heap_total_bytes;
/* Number of bytes free in the heap */
uint32_t heap_free_bytes;
/* Number of packets that were dropped because the transmit queue was full. */
uint16_t num_tx_dropped;
} meshtastic_LocalStats;
/* Health telemetry metrics */
@@ -456,7 +454,7 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
@@ -465,7 +463,7 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
@@ -553,7 +551,6 @@ extern "C" {
#define meshtastic_LocalStats_num_tx_relay_canceled_tag 11
#define meshtastic_LocalStats_heap_total_bytes_tag 12
#define meshtastic_LocalStats_heap_free_bytes_tag 13
#define meshtastic_LocalStats_num_tx_dropped_tag 14
#define meshtastic_HealthMetrics_heart_bpm_tag 1
#define meshtastic_HealthMetrics_spO2_tag 2
#define meshtastic_HealthMetrics_temperature_tag 3
@@ -675,8 +672,7 @@ X(a, STATIC, SINGULAR, UINT32, num_rx_dupe, 9) \
X(a, STATIC, SINGULAR, UINT32, num_tx_relay, 10) \
X(a, STATIC, SINGULAR, UINT32, num_tx_relay_canceled, 11) \
X(a, STATIC, SINGULAR, UINT32, heap_total_bytes, 12) \
X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13) \
X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14)
X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13)
#define meshtastic_LocalStats_CALLBACK NULL
#define meshtastic_LocalStats_DEFAULT NULL
@@ -753,7 +749,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
#define meshtastic_EnvironmentMetrics_size 113
#define meshtastic_HealthMetrics_size 11
#define meshtastic_HostMetrics_size 264
#define meshtastic_LocalStats_size 76
#define meshtastic_LocalStats_size 72
#define meshtastic_Nau7802Config_size 16
#define meshtastic_PowerMetrics_size 81
#define meshtastic_Telemetry_size 272

View File

@@ -94,13 +94,11 @@ static void onNetworkConnected()
// ESPmDNS (ESP32) and SimpleMDNS (RP2040) have slightly different APIs for adding TXT records
#ifdef ARCH_ESP32
MDNS.addServiceTxt("meshtastic", "tcp", "shortname", String(owner.short_name));
MDNS.addServiceTxt("meshtastic", "tcp", "id", String(nodeDB->getNodeId().c_str()));
MDNS.addServiceTxt("meshtastic", "tcp", "pio_env", optstr(APP_ENV));
MDNS.addServiceTxt("meshtastic", "tcp", "id", String(owner.id));
// ESP32 prints obtained IP address in WiFiEvent
#elif defined(ARCH_RP2040)
MDNS.addServiceTxt("meshtastic", "shortname", owner.short_name);
MDNS.addServiceTxt("meshtastic", "id", nodeDB->getNodeId().c_str());
MDNS.addServiceTxt("meshtastic", "pio_env", optstr(APP_ENV));
MDNS.addServiceTxt("meshtastic", "id", owner.id);
LOG_INFO("Obtained IP address: %s", WiFi.localIP().toString().c_str());
#endif
}

View File

@@ -554,8 +554,10 @@ void AdminModule::handleSetOwner(const meshtastic_User &o)
changed |= strcmp(owner.short_name, o.short_name);
strncpy(owner.short_name, o.short_name, sizeof(owner.short_name));
}
snprintf(owner.id, sizeof(owner.id), "!%08x", nodeDB->getNodeNum());
if (*o.id) {
changed |= strcmp(owner.id, o.id);
strncpy(owner.id, o.id, sizeof(owner.id));
}
if (owner.is_licensed != o.is_licensed) {
changed = 1;
owner.is_licensed = o.is_licensed;
@@ -609,9 +611,10 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
}
config.device = c.payload_variant.device;
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_NONE &&
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
meshtastic_Config_DeviceConfig_Role_REPEATER)) {
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
const char *warning = "Rebroadcast mode can't be set to NONE for a router";
const char *warning = "Rebroadcast mode can't be set to NONE for a router or repeater";
LOG_WARN(warning);
sendWarning(warning);
}
@@ -624,9 +627,8 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d", min_node_info_broadcast_secs);
config.device.node_info_broadcast_secs = min_node_info_broadcast_secs;
}
// Router Client and Repeater deprecated; Set it to client
if (IS_ONE_OF(c.payload_variant.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT,
meshtastic_Config_DeviceConfig_Role_REPEATER)) {
// Router Client is deprecated; Set it to client
if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
if (moduleConfig.store_forward.enabled && !moduleConfig.store_forward.is_server) {
moduleConfig.store_forward.is_server = true;
@@ -635,9 +637,10 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
}
}
#if USERPREFS_EVENT_MODE
// If we're in event mode, nobody is a Router or Router Late
// If we're in event mode, nobody is a Router or Repeater
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
}
#endif
@@ -704,40 +707,20 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
#endif
config.display = c.payload_variant.display;
break;
case meshtastic_Config_lora_tag: {
// Wrap the entire case in a block to scope variables and avoid crossing initialization
auto oldLoraConfig = config.lora;
auto validatedLora = c.payload_variant.lora;
case meshtastic_Config_lora_tag:
LOG_INFO("Set config: LoRa");
config.has_lora = true;
if (validatedLora.coding_rate < 4 || validatedLora.coding_rate > 8) {
LOG_WARN("Invalid coding_rate %d, setting to 5", validatedLora.coding_rate);
validatedLora.coding_rate = 5;
}
if (validatedLora.spread_factor < 7 || validatedLora.spread_factor > 12) {
LOG_WARN("Invalid spread_factor %d, setting to 11", validatedLora.spread_factor);
validatedLora.spread_factor = 11;
}
if (validatedLora.bandwidth == 0) {
int originalBandwidth = validatedLora.bandwidth;
validatedLora.bandwidth = myRegion->wideLora ? 800 : 250;
LOG_WARN("Invalid bandwidth %d, setting to default", originalBandwidth);
}
// If no lora radio parameters change, don't need to reboot
if (oldLoraConfig.use_preset == validatedLora.use_preset && oldLoraConfig.region == validatedLora.region &&
oldLoraConfig.modem_preset == validatedLora.modem_preset && oldLoraConfig.bandwidth == validatedLora.bandwidth &&
oldLoraConfig.spread_factor == validatedLora.spread_factor &&
oldLoraConfig.coding_rate == validatedLora.coding_rate && oldLoraConfig.tx_power == validatedLora.tx_power &&
oldLoraConfig.frequency_offset == validatedLora.frequency_offset &&
oldLoraConfig.override_frequency == validatedLora.override_frequency &&
oldLoraConfig.channel_num == validatedLora.channel_num &&
oldLoraConfig.sx126x_rx_boosted_gain == validatedLora.sx126x_rx_boosted_gain) {
if (config.lora.use_preset == c.payload_variant.lora.use_preset && config.lora.region == c.payload_variant.lora.region &&
config.lora.modem_preset == c.payload_variant.lora.modem_preset &&
config.lora.bandwidth == c.payload_variant.lora.bandwidth &&
config.lora.spread_factor == c.payload_variant.lora.spread_factor &&
config.lora.coding_rate == c.payload_variant.lora.coding_rate &&
config.lora.tx_power == c.payload_variant.lora.tx_power &&
config.lora.frequency_offset == c.payload_variant.lora.frequency_offset &&
config.lora.override_frequency == c.payload_variant.lora.override_frequency &&
config.lora.channel_num == c.payload_variant.lora.channel_num &&
config.lora.sx126x_rx_boosted_gain == c.payload_variant.lora.sx126x_rx_boosted_gain) {
requiresReboot = false;
}
@@ -756,7 +739,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
digitalWrite(RF95_FAN_EN, HIGH ^ 0);
}
#endif
config.lora = validatedLora;
config.lora = c.payload_variant.lora;
// If we're setting region for the first time, init the region and regenerate the keys
if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
if (!owner.is_licensed) {
@@ -782,26 +765,12 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
if (myRegion->dutyCycle < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
}
// Compare the entire string, we are sure of the length as a topic has never been set
if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) {
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
}
}
if (config.lora.region != myRegion->code) {
// Region has changed so check whether there is a regulatory one we should be using instead.
// Additionally as a side-effect, assume a new value under myRegion
initRegion();
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
// Default root is in use, so subscribe to the appropriate MQTT topic for this region
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
}
changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
}
break;
}
case meshtastic_Config_bluetooth_tag:
LOG_INFO("Set config: Bluetooth");
config.has_bluetooth = true;

View File

@@ -69,7 +69,7 @@ bool ascending = true;
#endif
#define EXT_NOTIFICATION_MODULE_OUTPUT_MS 1000
#define EXT_NOTIFICATION_FAST_THREAD_MS 25
#define EXT_NOTIFICATION_DEFAULT_THREAD_MS 25
#define ASCII_BELL 0x07
@@ -88,13 +88,12 @@ int32_t ExternalNotificationModule::runOnce()
if (!moduleConfig.external_notification.enabled) {
return INT32_MAX; // we don't need this thread here...
} else {
uint32_t delay = EXT_NOTIFICATION_MODULE_OUTPUT_MS;
bool isRtttlPlaying = rtttl::isPlaying();
bool isPlaying = rtttl::isPlaying();
#ifdef HAS_I2S
// audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop
isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
isPlaying = rtttl::isPlaying() || audioThread->isPlaying();
#endif
if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
if ((nagCycleCutoff < millis()) && !isPlaying) {
// let the song finish if we reach timeout
nagCycleCutoff = UINT32_MAX;
LOG_INFO("Turning off external notification: ");
@@ -117,16 +116,21 @@ int32_t ExternalNotificationModule::runOnce()
// If the output is turned on, turn it back off after the given period of time.
if (isNagging) {
delay = (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS);
if (externalTurnedOn[0] + delay < millis()) {
if (externalTurnedOn[0] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
setExternalState(0, !getExternal(0));
}
if (externalTurnedOn[1] + delay < millis()) {
if (externalTurnedOn[1] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
setExternalState(1, !getExternal(1));
}
// Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL)
if (!moduleConfig.external_notification.use_pwm && externalTurnedOn[2] + delay < millis()) {
if (!moduleConfig.external_notification.use_pwm &&
externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms,
millis());
setExternalState(2, !getExternal(2));
@@ -177,8 +181,6 @@ int32_t ExternalNotificationModule::runOnce()
colorState = 1;
}
}
// we need fast updates for the color change
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
#endif
#ifdef T_WATCH_S3
@@ -188,14 +190,12 @@ int32_t ExternalNotificationModule::runOnce()
// Play RTTTL over i2s audio interface if enabled as buzzer
#ifdef HAS_I2S
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
if (moduleConfig.external_notification.use_i2s_as_buzzer && canBuzz()) {
if (audioThread->isPlaying()) {
// Continue playing
} else if (isNagging && (nagCycleCutoff >= millis())) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
}
// we need fast updates to play the RTTTL
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
}
#endif
// now let the PWM buzzer play
@@ -206,11 +206,9 @@ int32_t ExternalNotificationModule::runOnce()
// start the song again if we have time left
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
}
// we need fast updates to play the RTTTL
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
}
return delay;
return EXT_NOTIFICATION_DEFAULT_THREAD_MS;
}
}
@@ -442,7 +440,7 @@ ExternalNotificationModule::ExternalNotificationModule()
ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp)
{
if (moduleConfig.external_notification.enabled && !isSilenced) {
if (moduleConfig.external_notification.enabled && !isMuted) {
#ifdef T_WATCH_S3
drv.setWaveform(0, 75);
drv.setWaveform(1, 56);
@@ -453,13 +451,12 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
// Check if the message contains a bell character. Don't do this loop for every pin, just once.
auto &p = mp.decoded;
bool containsBell = false;
for (size_t i = 0; i < p.payload.size; i++) {
for (int i = 0; i < p.payload.size; i++) {
if (p.payload.bytes[i] == ASCII_BELL) {
containsBell = true;
}
}
meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex());
if (moduleConfig.external_notification.alert_bell) {
if (containsBell) {
LOG_INFO("externalNotificationModule - Notification Bell");
@@ -510,8 +507,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
if (moduleConfig.external_notification.alert_message &&
(!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
if (moduleConfig.external_notification.alert_message) {
LOG_INFO("externalNotificationModule - Notification Module");
isNagging = true;
setExternalState(0, true);
@@ -522,8 +518,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
if (moduleConfig.external_notification.alert_message_vibra &&
(!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
if (moduleConfig.external_notification.alert_message_vibra) {
LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
isNagging = true;
setExternalState(1, true);
@@ -534,12 +529,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
if (moduleConfig.external_notification.alert_message_buzzer &&
(!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
if (moduleConfig.external_notification.alert_message_buzzer) {
LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(!isBroadcast(mp.to) && isToUs(&mp))) {
// Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
isNagging = true;
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
setExternalState(2, true);
@@ -558,10 +549,6 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
} else {
// Don't beep if buzzer mode is "direct messages only" and it is no direct message
LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY");
}
}
setIntervalFromNow(0); // run once so we know if we should do something
}

View File

@@ -43,8 +43,8 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
void setExternalState(uint8_t index = 0, bool on = false);
bool getExternal(uint8_t index = 0);
void setMute(bool mute) { isSilenced = mute; }
bool getMute() { return isSilenced; }
void setMute(bool mute) { isMuted = mute; }
bool getMute() { return isMuted; }
bool canBuzz();
bool nagging();
@@ -67,7 +67,7 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
bool isNagging = false;
bool isSilenced = false;
bool isMuted = false;
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,

View File

@@ -112,6 +112,7 @@
*/
void setupModules()
{
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
inputBroker = new InputBroker();
@@ -148,8 +149,8 @@ void setupModules()
}
#endif
#if !MESHTASTIC_EXCLUDE_ATAK
if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK ||
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) {
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK,
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) {
atakPluginModule = new AtakPluginModule();
}
#endif
@@ -298,6 +299,17 @@ void setupModules()
if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
new RangeTestModule();
#endif
} else {
#if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule();
#endif
#if HAS_TELEMETRY
new DeviceTelemetryModule();
#endif
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
traceRouteModule = new TraceRouteModule();
#endif
}
// NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra
// acks
routingModule = new RoutingModule();

View File

@@ -30,32 +30,14 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
bool wasBroadcast = isBroadcast(mp.to);
// LOG_DEBUG("did encode");
// if user has changed while packet was not for us, inform phone
if (hasChanged && !wasBroadcast && !isToUs(&mp)) {
auto packetCopy = packetPool.allocCopy(mp); // Keep a copy of the packet for later analysis
// Re-encode the user protobuf, as we have stripped out the user.id
packetCopy->decoded.payload.size = pb_encode_to_bytes(
packetCopy->decoded.payload.bytes, sizeof(packetCopy->decoded.payload.bytes), &meshtastic_User_msg, &p);
service->sendToPhone(packetCopy);
}
if (hasChanged && !wasBroadcast && !isToUs(&mp))
service->sendToPhone(packetPool.allocCopy(mp));
// LOG_DEBUG("did handleReceived");
return false; // Let others look at this message also if they want
}
void NodeInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p)
{
// Coerce user.id to be derived from the node number
snprintf(p->id, sizeof(p->id), "!%08x", getFrom(&mp));
// Re-encode the altered protobuf back into the packet
mp.decoded.payload.size =
pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_User_msg, p);
}
void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t channel, bool _shorterTimeout)
{
// cancel any not yet sent (now stale) position packets
@@ -112,12 +94,11 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
u.public_key.bytes[0] = 0;
u.public_key.size = 0;
}
// FIXME: Clear the user.id field since it should be derived from node number on the receiving end
// u.id[0] = '\0';
// Ensure our user.id is derived correctly
strcpy(u.id, nodeDB->getNodeId().c_str());
// Coerce unmessagable for Repeater role
if (u.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
u.has_is_unmessagable = true;
u.is_unmessagable = true;
}
LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name);
lastSentToMesh = millis();

View File

@@ -30,9 +30,6 @@ class NodeInfoModule : public ProtobufModule<meshtastic_User>, private concurren
*/
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *p) override;
/** Called to alter received User protobuf */
virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p) override;
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual meshtastic_MeshPacket *allocReply() override;

View File

@@ -41,12 +41,12 @@ int32_t RangeTestModule::runOnce()
// moduleConfig.range_test.enabled = 1;
// moduleConfig.range_test.sender = 30;
// moduleConfig.range_test.save = 1;
// moduleConfig.range_test.clear_on_reboot = 1;
// Fixed position is useful when testing indoors.
// config.position.fixed_position = 1;
uint32_t senderHeartbeat = moduleConfig.range_test.sender * 1000;
if (moduleConfig.range_test.enabled) {
if (firstTime) {
@@ -54,11 +54,6 @@ int32_t RangeTestModule::runOnce()
firstTime = 0;
if (moduleConfig.range_test.clear_on_reboot) {
// User wants to delete previous range test(s)
LOG_INFO("Range Test Module - Clearing out previous test file");
rangeTestModuleRadio->removeFile();
}
if (moduleConfig.range_test.sender) {
LOG_INFO("Init Range Test Module -- Sender");
started = millis(); // make a note of when we started
@@ -146,6 +141,7 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
*/
if (!isFromUs(&mp)) {
if (moduleConfig.range_test.save) {
appendFile(mp);
}
@@ -299,42 +295,7 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
fileToAppend.printf("\"%s\"\n", p.payload.bytes);
fileToAppend.flush();
fileToAppend.close();
#endif
return 1;
#else
LOG_ERROR("Failed to store range test results - feature only available for ESP32");
return 0;
#endif
}
bool RangeTestModuleRadio::removeFile()
{
#ifdef ARCH_ESP32
if (!FSBegin()) {
LOG_DEBUG("An Error has occurred while mounting the filesystem");
return 0;
}
if (!FSCom.exists("/static/rangetest.csv")) {
LOG_DEBUG("No range tests found.");
return 0;
}
LOG_INFO("Deleting previous range test.");
bool result = FSCom.remove("/static/rangetest.csv");
if (!result) {
LOG_ERROR("Failed to delete range test.");
return 0;
}
LOG_INFO("Range test removed.");
return 1;
#else
LOG_ERROR("Failed to remove range test results - feature only available for ESP32");
return 0;
#endif
}

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