diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml
index f7bf95f83..f79e4fdb5 100644
--- a/.github/actionlint.yaml
+++ b/.github/actionlint.yaml
@@ -2,4 +2,5 @@
self-hosted-runner:
# Labels of self-hosted runner in array of strings.
labels:
+ - arctastic
- test-runner
diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml
index a1e8dd852..c048b7ac2 100644
--- a/.github/actions/build-variant/action.yml
+++ b/.github/actions/build-variant/action.yml
@@ -76,7 +76,7 @@ runs:
done
- name: PlatformIO ${{ inputs.arch }} download cache
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: ~/.platformio/.cache
key: pio-cache-${{ inputs.arch }}-${{ hashFiles('.github/actions/**', '**.ini') }}
@@ -100,7 +100,7 @@ runs:
id: version
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}
overwrite: true
diff --git a/.github/workflows/build_debian_src.yml b/.github/workflows/build_debian_src.yml
index d7d26f0e8..de114be1c 100644
--- a/.github/workflows/build_debian_src.yml
+++ b/.github/workflows/build_debian_src.yml
@@ -64,7 +64,7 @@ jobs:
PKG_VERSION: ${{ steps.version.outputs.deb }}
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
overwrite: true
diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml
index c3b70d4c9..d384540a4 100644
--- a/.github/workflows/build_firmware.yml
+++ b/.github/workflows/build_firmware.yml
@@ -18,7 +18,8 @@ permissions: read-all
jobs:
pio-build:
name: build-${{ inputs.platform }}
- runs-on: ubuntu-24.04
+ # Use 'arctastic' self-hosted runner pool when building in the main repo
+ runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
@@ -55,19 +56,21 @@ jobs:
ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }}
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
- - name: Echo manifest from release/firmware-*.mt.json to job summary
- if: ${{ always() }}
+ - name: Job summary
env:
PIO_ENV: ${{ inputs.pio_env }}
run: |
- echo "## Manifest: \`$PIO_ENV\`" >> $GITHUB_STEP_SUMMARY
+ echo "## $PIO_ENV" >> $GITHUB_STEP_SUMMARY
+ echo "Manifest
" >> $GITHUB_STEP_SUMMARY
+ echo '' >> $GITHUB_STEP_SUMMARY
echo '```json' >> $GITHUB_STEP_SUMMARY
cat release/firmware-*.mt.json >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
+ echo " " >> $GITHUB_STEP_SUMMARY
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
id: upload
with:
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml
index 02aad5a9c..9cc0bac78 100644
--- a/.github/workflows/build_one_target.yml
+++ b/.github/workflows/build_one_target.yml
@@ -98,7 +98,7 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
path: ./
pattern: firmware-*-*
@@ -111,7 +111,7 @@ jobs:
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -127,7 +127,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
pattern: firmware-*-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -139,14 +139,14 @@ jobs:
- name: Device scripts permissions
run: |
- chmod +x ./output/device-install.sh
- chmod +x ./output/device-update.sh
+ chmod +x ./output/device-install.sh || true
+ chmod +x ./output/device-update.sh || true
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
overwrite: true
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index b4f4c3d11..d7bde7bc5 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -77,16 +77,21 @@ jobs:
fail-fast: false
matrix:
check: ${{ fromJson(needs.setup.outputs.check) }}
-
- runs-on: ubuntu-latest
+ # Use 'arctastic' self-hosted runner pool when checking in the main repo
+ runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
steps:
- uses: actions/checkout@v6
- - name: Build base
- id: base
- uses: ./.github/actions/setup-base
+ with:
+ submodules: recursive
+ ref: ${{github.event.pull_request.head.ref}}
+ repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Check ${{ matrix.check.board }}
- run: bin/check-all.sh ${{ matrix.check.board }}
+ uses: meshtastic/gh-action-firmware@main
+ with:
+ pio_platform: ${{ matrix.check.platform }}
+ pio_env: ${{ matrix.check.board }}
+ pio_target: check
build:
needs: [setup, version]
@@ -168,7 +173,7 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
path: ./
pattern: firmware-${{matrix.arch}}-*
@@ -178,7 +183,7 @@ jobs:
run: ls -R
- name: Repackage in single firmware zip
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -195,7 +200,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -207,14 +212,14 @@ jobs:
- name: Device scripts permissions
run: |
- chmod +x ./output/device-install.sh
- chmod +x ./output/device-update.sh
+ chmod +x ./output/device-install.sh || true
+ chmod +x ./output/device-update.sh || true
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -255,14 +260,14 @@ jobs:
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
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@v6
+ uses: actions/download-artifact@v7
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -280,13 +285,13 @@ jobs:
- name: Generate Release manifest
run: |
- jq -n --arg ver "${{ needs.version.outputs.long }}" --arg targets "${{ toJson(needs.setup.outputs.all) }}" '{
+ jq -n --arg ver "${{ needs.version.outputs.long }}" --argjson targets ${{ toJson(needs.setup.outputs.all) }} '{
"version": $ver,
- "targets": ($targets | fromjson)
+ "targets": $targets
}' > firmware-${{ needs.version.outputs.long }}.json
- name: Save Release manifest artifact
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: manifest-${{ needs.version.outputs.long }}
overwrite: true
@@ -327,7 +332,7 @@ jobs:
with:
python-version: 3.x
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -338,13 +343,13 @@ jobs:
- name: Device scripts permissions
run: |
- chmod +x ./output/device-install.sh
- chmod +x ./output/device-update.sh
+ chmod +x ./output/device-install.sh || true
+ chmod +x ./output/device-update.sh || true
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -383,14 +388,14 @@ jobs:
python-version: 3.x
- name: Get firmware artifacts
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Get manifest artifact
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
pattern: manifest-${{ needs.version.outputs.long }}
path: ./publish
diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml
index b9bb3ceed..bd3f6d4eb 100644
--- a/.github/workflows/merge_queue.yml
+++ b/.github/workflows/merge_queue.yml
@@ -147,7 +147,7 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
path: ./
pattern: firmware-${{matrix.arch}}-*
@@ -160,7 +160,7 @@ jobs:
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -176,7 +176,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -188,14 +188,14 @@ jobs:
- name: Device scripts permissions
run: |
- chmod +x ./output/device-install.sh
- chmod +x ./output/device-update.sh
+ chmod +x ./output/device-install.sh || true
+ chmod +x ./output/device-update.sh || true
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -235,14 +235,14 @@ jobs:
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
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@v6
+ uses: actions/download-artifact@v7
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -292,7 +292,7 @@ jobs:
with:
python-version: 3.x
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -303,13 +303,13 @@ jobs:
- name: Device scripts permissions
run: |
- chmod +x ./output/device-install.sh
- chmod +x ./output/device-update.sh
+ chmod +x ./output/device-install.sh || true
+ chmod +x ./output/device-update.sh || true
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -347,7 +347,7 @@ jobs:
with:
python-version: 3.x
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
diff --git a/.github/workflows/package_obs.yml b/.github/workflows/package_obs.yml
index 2b202ed95..63f1fe8a0 100644
--- a/.github/workflows/package_obs.yml
+++ b/.github/workflows/package_obs.yml
@@ -58,7 +58,7 @@ jobs:
id: version
- name: Download artifacts
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true
diff --git a/.github/workflows/package_pio_deps.yml b/.github/workflows/package_pio_deps.yml
index cb10a79f3..82ffe66e9 100644
--- a/.github/workflows/package_pio_deps.yml
+++ b/.github/workflows/package_pio_deps.yml
@@ -56,7 +56,7 @@ jobs:
PLATFORMIO_CORE_DIR: pio/core
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
overwrite: true
diff --git a/.github/workflows/package_ppa.yml b/.github/workflows/package_ppa.yml
index 2e3278041..9a463dbea 100644
--- a/.github/workflows/package_ppa.yml
+++ b/.github/workflows/package_ppa.yml
@@ -60,7 +60,7 @@ jobs:
id: version
- name: Download artifacts
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true
diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml
index a3e0b23cf..6306d777f 100644
--- a/.github/workflows/pr_tests.yml
+++ b/.github/workflows/pr_tests.yml
@@ -50,7 +50,7 @@ jobs:
- name: Download test artifacts
if: needs.native-tests.result != 'skipped'
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: platformio-test-report-${{ steps.version.outputs.long }}
merge-multiple: true
diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml
index d044f9038..d93449d6d 100644
--- a/.github/workflows/sec_sast_semgrep_cron.yml
+++ b/.github/workflows/sec_sast_semgrep_cron.yml
@@ -33,7 +33,7 @@ jobs:
# step 3
- name: save report as pipeline artifact
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: report.sarif
overwrite: true
diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml
index 26ff306a9..cabe0dd97 100644
--- a/.github/workflows/test_native.yml
+++ b/.github/workflows/test_native.yml
@@ -59,7 +59,7 @@ jobs:
id: version
- name: Save coverage information
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
if: always() # run this step even if previous step failed
with:
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}
@@ -94,7 +94,7 @@ jobs:
- name: Save test results
if: always() # run this step even if previous step failed
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: platformio-test-report-${{ steps.version.outputs.long }}
overwrite: true
@@ -108,7 +108,7 @@ jobs:
sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative.
- name: Save coverage information
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
if: always() # run this step even if previous step failed
with:
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}
@@ -137,7 +137,7 @@ jobs:
id: version
- name: Download test artifacts
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
name: platformio-test-report-${{ steps.version.outputs.long }}
merge-multiple: true
@@ -150,7 +150,7 @@ jobs:
reporter: java-junit
- name: Download coverage artifacts
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}
path: code-coverage-report
@@ -163,7 +163,7 @@ jobs:
genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report
- name: Save Code Coverage Report
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: code-coverage-report-${{ steps.version.outputs.long }}
path: code-coverage-report
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 565433c38..705f0177c 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -8,17 +8,17 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- - checkov@3.2.495
- - renovate@42.42.2
+ - checkov@3.2.496
+ - renovate@42.66.14
- prettier@3.7.4
- - trufflehog@3.92.1
+ - trufflehog@3.92.4
- yamllint@1.37.1
- bandit@1.9.2
- - trivy@0.68.1
+ - trivy@0.68.2
- taplo@0.10.0
- - ruff@0.14.8
+ - ruff@0.14.10
- isort@7.0.0
- - markdownlint@0.46.0
+ - markdownlint@0.47.0
- oxipng@10.0.0
- svgo@4.0.0
- actionlint@1.7.9
diff --git a/bin/build-esp32.sh b/bin/build-esp32.sh
index 8c684aa7e..4e799b30a 100755
--- a/bin/build-esp32.sh
+++ b/bin/build-esp32.sh
@@ -22,7 +22,7 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
-pio run --environment $1 # -v
+pio run --environment $1 -t mtjson # -v
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
@@ -32,20 +32,10 @@ cp $BUILDDIR/$basename.factory.bin $OUTDIR/$basename.factory.bin
echo "Copying ESP32 update bin file"
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
-echo "Building Filesystem for ESP32 targets"
-# If you want to build the webui, uncomment the following lines
-# pio run --environment $1 -t buildfs
-# cp .pio/build/$1/littlefs.bin $OUTDIR/littlefswebui-$1-$VERSION.bin
-# # Remove webserver files from the filesystem and rebuild
-# ls -l data/static # Diagnostic list of files
-# rm -rf data/static
-pio run --environment $1 -t buildfs --disable-auto-clean
+echo "Copying Filesystem for ESP32 targets"
cp $BUILDDIR/littlefs-$1-$VERSION.bin $OUTDIR/littlefs-$1-$VERSION.bin
cp bin/device-install.* $OUTDIR/
cp bin/device-update.* $OUTDIR/
-# Generate the manifest file
-echo "Generating Meshtastic manifest"
-TIMEFORMAT="Generated manifest in %E seconds"
-time pio run --environment $1 -t mtjson --silent --disable-auto-clean
+echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh
index c605fb1e0..edcc2add2 100755
--- a/bin/build-nrf52.sh
+++ b/bin/build-nrf52.sh
@@ -21,13 +21,14 @@ rm -f $BUILDDIR/firmware*
export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
+ota_basename=${basename}-ota
-pio run --environment $1 # -v
+pio run --environment $1 -t mtjson # -v
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Copying NRF52 dfu (OTA) file"
-cp $BUILDDIR/$basename.zip $OUTDIR/$basename.zip
+cp $BUILDDIR/$basename.zip $OUTDIR/$ota_basename.zip
echo "Copying NRF52 UF2 file"
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
@@ -47,8 +48,5 @@ if (echo $1 | grep -q "rak4631"); then
cp $SRCHEX $OUTDIR/
fi
-# Generate the manifest file
-echo "Generating Meshtastic manifest"
-TIMEFORMAT="Generated manifest in %E seconds"
-time pio run --environment $1 -t mtjson --silent --disable-auto-clean
+echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
diff --git a/bin/build-rp2xx0.sh b/bin/build-rp2xx0.sh
index ae26fdfbf..3ef1c1e34 100755
--- a/bin/build-rp2xx0.sh
+++ b/bin/build-rp2xx0.sh
@@ -22,15 +22,12 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
-pio run --environment $1 # -v
+pio run --environment $1 -t mtjson # -v
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Copying uf2 file"
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
-# Generate the manifest file
-echo "Generating Meshtastic manifest"
-TIMEFORMAT="Generated manifest in %E seconds"
-time pio run --environment $1 -t mtjson --silent --disable-auto-clean
+echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
diff --git a/bin/build-stm32wl.sh b/bin/build-stm32wl.sh
index b85da04a6..023f3603c 100755
--- a/bin/build-stm32wl.sh
+++ b/bin/build-stm32wl.sh
@@ -22,15 +22,12 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
-pio run --environment $1 # -v
+pio run --environment $1 -t mtjson # -v
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Copying STM32 bin file"
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
-# Generate the manifest file
-echo "Generating Meshtastic manifest"
-TIMEFORMAT="Generated manifest in %E seconds"
-time pio run --environment $1 -t mtjson --silent --disable-auto-clean
+echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml
index b4cc81792..adf804ba9 100644
--- a/bin/config-dist.yaml
+++ b/bin/config-dist.yaml
@@ -184,6 +184,8 @@ Input:
Logging:
LogLevel: info # debug, info, warn, error
# TraceFile: /var/log/meshtasticd.json
+# JSONFile: /packets.json # File location for JSON output of decoded packets
+# JSONFilter: position # filter for packets to save to JSON file
# AsciiLogs: true # default if not specified is !isatty() on stdout
Webserver:
diff --git a/bin/exception_decoder.py b/bin/exception_decoder.py
index ec94ce20e..ffe6d3f24 100755
--- a/bin/exception_decoder.py
+++ b/bin/exception_decoder.py
@@ -75,7 +75,7 @@ TOOLS = {
}
BACKTRACE_REGEX = re.compile(
- r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b"
+ r"\b(0x4[0-9a-fA-F]{7,8}):0x[0-9a-fA-F]{8}\b"
)
EXCEPTION_REGEX = re.compile("^Exception \\((?P[0-9]*)\\):$")
COUNTER_REGEX = re.compile(
@@ -89,7 +89,7 @@ POINTER_REGEX = re.compile(
STACK_BEGIN = ">>>stack>>>"
STACK_END = "<<[0-9a-f]+):\W+(?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+)(\W.*)?$"
+ r"^(?P[0-9a-f]+):\W+(?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+)(\W.*)?$"
)
StackLine = namedtuple("StackLine", ["offset", "content"])
@@ -223,7 +223,7 @@ class AddressResolver(object):
if match is None:
if last is not None and line.startswith("(inlined by)"):
line = line[12:].strip()
- self._address_map[last] += "\n \-> inlined by: " + line
+ self._address_map[last] += "\n \\-> inlined by: " + line
continue
if match.group("result") == "?? ??:0":
diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py
index 151cf0a97..b6560f35b 100644
--- a/bin/platformio-custom.py
+++ b/bin/platformio-custom.py
@@ -17,6 +17,8 @@ lfsbin = f"{progname.replace('firmware-', 'littlefs-')}.bin"
def manifest_gather(source, target, env):
out = []
+ board_platform = env.BoardConfig().get("platform")
+ needs_ota_suffix = board_platform == "nordicnrf52"
check_paths = [
progname,
f"{progname}.elf",
@@ -32,8 +34,11 @@ def manifest_gather(source, target, env):
for p in check_paths:
f = env.File(env.subst(f"$BUILD_DIR/{p}"))
if f.exists():
+ manifest_name = p
+ if needs_ota_suffix and p == f"{progname}.zip":
+ manifest_name = f"{progname}-ota.zip"
d = {
- "name": p,
+ "name": manifest_name,
"md5": f.get_content_hash(), # Returns MD5 hash
"bytes": f.get_size() # Returns file size in bytes
}
@@ -159,20 +164,22 @@ def load_boot_logo(source, target, env):
# Load the boot logo on TFT builds
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
- env.AddPreAction('$BUILD_DIR/littlefs.bin', load_boot_logo)
+ env.AddPreAction(f"$BUILD_DIR/{lfsbin}", load_boot_logo)
-# Rename (mv) littlefs.bin to include the PROGNAME
-# This ensures the littlefs.bin is named consistently with the firmware
-env.AddPostAction('$BUILD_DIR/littlefs.bin', env.VerboseAction(
- f'mv $BUILD_DIR/littlefs.bin $BUILD_DIR/{lfsbin}',
- f'Renaming littlefs.bin to {lfsbin}'
-))
+mtjson_deps = ["buildprog"]
+if platform.name == "espressif32":
+ # Build littlefs image as part of mtjson target
+ # Equivalent to `pio run -t buildfs`
+ target_lfs = env.DataToBin(
+ join("$BUILD_DIR", "${ESP32_FS_IMAGE_NAME}"), "$PROJECT_DATA_DIR"
+ )
+ mtjson_deps.append(target_lfs)
env.AddCustomTarget(
name="mtjson",
- dependencies=None,
+ dependencies=mtjson_deps,
actions=[manifest_gather],
title="Meshtastic Manifest",
description="Generating Meshtastic manifest JSON + Checksums",
- always_build=True,
+ always_build=False,
)
diff --git a/bin/platformio-pre.py b/bin/platformio-pre.py
index 4e51a6544..16278b813 100644
--- a/bin/platformio-pre.py
+++ b/bin/platformio-pre.py
@@ -11,6 +11,9 @@ else:
prefsLoc = env["PROJECT_DIR"] + "/version.properties"
verObj = readProps(prefsLoc)
env.Replace(PROGNAME=f"firmware-{env.get('PIOENV')}-{verObj['long']}")
+ env.Replace(ESP32_FS_IMAGE_NAME=f"littlefs-{env.get('PIOENV')}-{verObj['long']}")
# Print the new program name for verification
print(f"PROGNAME: {env.get('PROGNAME')}")
+if platform.name == "espressif32":
+ print(f"ESP32_FS_IMAGE_NAME: {env.get('ESP32_FS_IMAGE_NAME')}")
diff --git a/platformio.ini b/platformio.ini
index ed784ae07..b9b288576 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -64,7 +64,7 @@ monitor_speed = 115200
monitor_filters = direct
lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
- https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip
+ https://github.com/meshtastic/esp8266-oled-ssd1306/archive/b34c6817c25d6faabb3a8a162b5d14fb75395433.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
@@ -207,7 +207,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library
sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
- ClosedCube OPT3001@1.1.2
+ closedcube/ClosedCube OPT3001@1.1.2
# renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2
boschsensortec/bsec2@1.10.2610
# renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library
diff --git a/protobufs b/protobufs
index 4095e5989..9beb80f1d 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 4095e598902b4cd893dbcb62842514704d0f64e0
+Subproject commit 9beb80f1d302f70d05f9c4bc9dd543b8f7bc8796
diff --git a/src/DisplayFormatters.cpp b/src/DisplayFormatters.cpp
index 246cf0022..d88f9fc9f 100644
--- a/src/DisplayFormatters.cpp
+++ b/src/DisplayFormatters.cpp
@@ -31,6 +31,9 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
return useShortName ? "LongF" : "LongFast";
break;
+ case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
+ return useShortName ? "LongT" : "LongTurbo";
+ break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
return useShortName ? "LongM" : "LongMod";
break;
diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp
index 1b3a148d6..ceb3b83f5 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -532,8 +532,10 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
const int labelX = x;
int barsOffset = (isHighResolution) ? 24 : 0;
#ifdef USE_EINK
+#ifndef T_DECK_PRO
barsOffset -= 12;
#endif
+#endif
#if defined(M5STACK_UNITC6L)
const int barX = x + 45 + barsOffset;
#else
@@ -574,7 +576,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
#endif
// Value string
display->setTextAlignment(TEXT_ALIGN_RIGHT);
- display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
+ display->drawString(SCREEN_WIDTH, getTextPositions(display)[line], combinedStr);
};
// === Memory values ===
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index f782dabb6..586bdd4a6 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -20,12 +20,41 @@
#include "modules/KeyVerificationModule.h"
#include "modules/TraceRouteModule.h"
+#include
+#include
#include
+#include
extern uint16_t TFT_MESH;
namespace graphics
{
+
+namespace
+{
+
+// Caller must ensure the provided options array outlives the banner callback.
+template
+BannerOverlayOptions createStaticBannerOptions(const char *message, const MenuOption (&options)[N],
+ std::array &labels, Callback &&onSelection)
+{
+ for (size_t i = 0; i < N; ++i) {
+ labels[i] = options[i].label;
+ }
+
+ const MenuOption *optionsPtr = options;
+ auto callback = std::function &, int)>(std::forward(onSelection));
+
+ BannerOverlayOptions bannerOptions;
+ bannerOptions.message = message;
+ bannerOptions.optionsArrayPtr = labels.data();
+ bannerOptions.optionsCount = static_cast(N);
+ bannerOptions.bannerCallback = [optionsPtr, callback](int selected) -> void { callback(optionsPtr[selected], selected); };
+ return bannerOptions;
+}
+
+} // namespace
+
menuHandler::screenMenus menuHandler::menuQueue = menu_none;
bool test_enabled = false;
uint8_t test_count = 0;
@@ -197,48 +226,38 @@ void menuHandler::DeviceRolePicker()
void menuHandler::RadioPresetPicker()
{
- static const char *optionsArray[] = {"Back", "LongSlow", "LongModerate", "LongFast", "MediumSlow",
- "MediumFast", "ShortSlow", "ShortFast", "ShortTurbo"};
- enum optionsNumbers {
- Back = 0,
- radiopreset_LongSlow = 1,
- radiopreset_LongModerate = 2,
- radiopreset_LongFast = 3,
- radiopreset_MediumSlow = 4,
- radiopreset_MediumFast = 5,
- radiopreset_ShortSlow = 6,
- radiopreset_ShortFast = 7,
- radiopreset_ShortTurbo = 8
- };
- BannerOverlayOptions bannerOptions;
- bannerOptions.message = "Radio Preset";
- bannerOptions.optionsArrayPtr = optionsArray;
- bannerOptions.optionsCount = 9;
- bannerOptions.bannerCallback = [](int selected) -> void {
- if (selected == Back) {
- menuHandler::menuQueue = menuHandler::lora_Menu;
- screen->runNow();
- return;
- } else if (selected == radiopreset_LongSlow) {
- config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW;
- } else if (selected == radiopreset_LongModerate) {
- config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE;
- } else if (selected == radiopreset_LongFast) {
- config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
- } else if (selected == radiopreset_MediumSlow) {
- config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW;
- } else if (selected == radiopreset_MediumFast) {
- config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST;
- } else if (selected == radiopreset_ShortSlow) {
- config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW;
- } else if (selected == radiopreset_ShortFast) {
- config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST;
- } else if (selected == radiopreset_ShortTurbo) {
- config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO;
- }
- service->reloadConfig(SEGMENT_CONFIG);
- rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
+ static const RadioPresetOption presetOptions[] = {
+ {"Back", OptionsAction::Back},
+ {"LongTurbo", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO},
+ {"LongModerate", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE},
+ {"LongFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST},
+ {"MediumSlow", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW},
+ {"MediumFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST},
+ {"ShortSlow", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW},
+ {"ShortFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST},
+ {"ShortTurbo", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO},
};
+
+ constexpr size_t presetCount = sizeof(presetOptions) / sizeof(presetOptions[0]);
+ static std::array presetLabels{};
+
+ auto bannerOptions =
+ createStaticBannerOptions("Radio Preset", presetOptions, presetLabels, [](const RadioPresetOption &option, int) -> void {
+ if (option.action == OptionsAction::Back) {
+ menuHandler::menuQueue = menuHandler::lora_Menu;
+ screen->runNow();
+ return;
+ }
+
+ if (!option.hasValue) {
+ return;
+ }
+
+ config.lora.modem_preset = option.value;
+ service->reloadConfig(SEGMENT_CONFIG);
+ rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
+ });
+
screen->showOverlayBanner(bannerOptions);
}
@@ -1041,12 +1060,13 @@ void menuHandler::switchToMUIMenu()
void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
{
- static const char *optionsArray[] = {"Back", "Default", "Meshtastic Green", "Yellow", "Red", "Orange", "Purple", "Teal",
- "Pink", "White"};
+ static const char *optionsArray[] = {
+ "Back", "Default", "Meshtastic Green", "Yellow", "Red", "Orange", "Purple", "Blue", "Teal", "Cyan", "Ice", "Pink",
+ "White", "Gray"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Select Screen Color";
bannerOptions.optionsArrayPtr = optionsArray;
- bannerOptions.optionsCount = 10;
+ bannerOptions.optionsCount = 14;
bannerOptions.bannerCallback = [display](int selected) -> void {
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \
HAS_TFT || defined(HACKADAY_COMMUNICATOR)
@@ -1082,20 +1102,40 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
TFT_MESH_g = 153;
TFT_MESH_b = 255;
} else if (selected == 7) {
- LOG_INFO("Setting color to Teal");
- TFT_MESH_r = 64;
- TFT_MESH_g = 224;
- TFT_MESH_b = 208;
+ LOG_INFO("Setting color to Blue");
+ TFT_MESH_r = 0;
+ TFT_MESH_g = 0;
+ TFT_MESH_b = 255;
} else if (selected == 8) {
+ LOG_INFO("Setting color to Teal");
+ TFT_MESH_r = 16;
+ TFT_MESH_g = 102;
+ TFT_MESH_b = 102;
+ } else if (selected == 9) {
+ LOG_INFO("Setting color to Cyan");
+ TFT_MESH_r = 0;
+ TFT_MESH_g = 255;
+ TFT_MESH_b = 255;
+ } else if (selected == 10) {
+ LOG_INFO("Setting color to Ice");
+ TFT_MESH_r = 173;
+ TFT_MESH_g = 216;
+ TFT_MESH_b = 230;
+ } else if (selected == 11) {
LOG_INFO("Setting color to Pink");
TFT_MESH_r = 255;
TFT_MESH_g = 105;
TFT_MESH_b = 180;
- } else if (selected == 9) {
+ } else if (selected == 12) {
LOG_INFO("Setting color to White");
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
+ } else if (selected == 13) {
+ LOG_INFO("Setting color to Gray");
+ TFT_MESH_r = 128;
+ TFT_MESH_g = 128;
+ TFT_MESH_b = 128;
} else {
menuQueue = system_base_menu;
screen->runNow();
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index a611b7c9d..df7c2739b 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -99,5 +99,24 @@ class menuHandler
static void BluetoothToggleMenu();
};
+/* Generic Menu Options designations */
+enum class OptionsAction { Back, Select };
+
+template struct MenuOption {
+ const char *label;
+ OptionsAction action;
+ bool hasValue;
+ T value;
+
+ MenuOption(const char *labelIn, OptionsAction actionIn, T valueIn)
+ : label(labelIn), action(actionIn), hasValue(true), value(valueIn)
+ {
+ }
+
+ MenuOption(const char *labelIn, OptionsAction actionIn) : label(labelIn), action(actionIn), hasValue(false), value() {}
+};
+
+using RadioPresetOption = MenuOption;
+
} // namespace graphics
#endif
\ No newline at end of file
diff --git a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp
index 88bed998d..ad0f9fc47 100644
--- a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp
@@ -1,6 +1,7 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./PositionsApplet.h"
+#include "NodeDB.h"
using namespace NicheGraphics;
@@ -49,8 +50,8 @@ ProcessMessage InkHUD::PositionsApplet::handleReceived(const meshtastic_MeshPack
if (!hasPosition)
return ProcessMessage::CONTINUE;
- bool hasHopsAway = (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start); // From NodeDB::updateFrom
- uint8_t hopsAway = mp.hop_start - mp.hop_limit;
+ const int8_t hopsAway = getHopsAway(mp);
+ const bool hasHopsAway = hopsAway >= 0;
// Determine if the position packet would change anything on-screen
// -----------------------------------------------------------------
diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h
index 022101f7d..c55d7fa53 100644
--- a/src/input/InputBroker.h
+++ b/src/input/InputBroker.h
@@ -53,6 +53,7 @@ typedef struct _InputEvent {
class InputPollable
{
public:
+ virtual ~InputPollable() = default;
virtual void pollOnce() = 0;
};
diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp
index 7b43fa256..cc1222595 100644
--- a/src/input/RotaryEncoderImpl.cpp
+++ b/src/input/RotaryEncoderImpl.cpp
@@ -3,6 +3,9 @@
#include "RotaryEncoderImpl.h"
#include "InputBroker.h"
#include "RotaryEncoder.h"
+#ifdef ARCH_ESP32
+#include "sleep.h"
+#endif
#define ORIGIN_NAME "RotaryEncoder"
@@ -11,6 +14,20 @@ RotaryEncoderImpl *rotaryEncoderImpl;
RotaryEncoderImpl::RotaryEncoderImpl()
{
rotary = nullptr;
+#ifdef ARCH_ESP32
+ isFirstInit = true;
+#endif
+}
+
+RotaryEncoderImpl::~RotaryEncoderImpl()
+{
+ LOG_DEBUG("RotaryEncoderImpl destructor");
+ detachRotaryEncoderInterrupts();
+
+ if (rotary != nullptr) {
+ delete rotary;
+ rotary = nullptr;
+ }
}
bool RotaryEncoderImpl::init()
@@ -25,15 +42,22 @@ bool RotaryEncoderImpl::init()
eventCcw = static_cast(moduleConfig.canned_message.inputbroker_event_ccw);
eventPressed = static_cast(moduleConfig.canned_message.inputbroker_event_press);
- rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b,
- moduleConfig.canned_message.inputbroker_pin_press);
- rotary->resetButton();
+ if (rotary == nullptr) {
+ rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b,
+ moduleConfig.canned_message.inputbroker_pin_press);
+ }
- 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);
+ attachRotaryEncoderInterrupts();
+
+#ifdef ARCH_ESP32
+ // Register callbacks for before and after lightsleep
+ // Used to detach and reattach interrupts
+ if (isFirstInit) {
+ lsObserver.observe(¬ifyLightSleep);
+ lsEndObserver.observe(¬ifyLightSleepEnd);
+ isFirstInit = false;
+ }
+#endif
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,
@@ -71,6 +95,50 @@ void RotaryEncoderImpl::pollOnce()
}
}
+void RotaryEncoderImpl::detachRotaryEncoderInterrupts()
+{
+ LOG_DEBUG("RotaryEncoderImpl detach button interrupts");
+ if (interruptInstance == this) {
+ detachInterrupt(moduleConfig.canned_message.inputbroker_pin_a);
+ detachInterrupt(moduleConfig.canned_message.inputbroker_pin_b);
+ detachInterrupt(moduleConfig.canned_message.inputbroker_pin_press);
+ interruptInstance = nullptr;
+ } else {
+ LOG_WARN("RotaryEncoderImpl: interrupts already detached");
+ }
+}
+
+void RotaryEncoderImpl::attachRotaryEncoderInterrupts()
+{
+ LOG_DEBUG("RotaryEncoderImpl attach button interrupts");
+ if (rotary != nullptr && interruptInstance == nullptr) {
+ 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);
+ } else {
+ LOG_WARN("RotaryEncoderImpl: interrupts already attached");
+ }
+}
+
+#ifdef ARCH_ESP32
+
+int RotaryEncoderImpl::beforeLightSleep(void *unused)
+{
+ detachRotaryEncoderInterrupts();
+ return 0; // Indicates success;
+}
+
+int RotaryEncoderImpl::afterLightSleep(esp_sleep_wakeup_cause_t cause)
+{
+ attachRotaryEncoderInterrupts();
+ return 0; // Indicates success;
+}
+#endif
+
RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
#endif
\ No newline at end of file
diff --git a/src/input/RotaryEncoderImpl.h b/src/input/RotaryEncoderImpl.h
index 6f8e9fe5f..ec8a064bd 100644
--- a/src/input/RotaryEncoderImpl.h
+++ b/src/input/RotaryEncoderImpl.h
@@ -8,12 +8,18 @@
class RotaryEncoder;
-class RotaryEncoderImpl : public InputPollable
+class RotaryEncoderImpl final : public InputPollable
{
public:
RotaryEncoderImpl();
- bool init(void);
+ ~RotaryEncoderImpl() override;
+ bool init();
virtual void pollOnce() override;
+ // Disconnect and reconnect interrupts for light sleep
+#ifdef ARCH_ESP32
+ int beforeLightSleep(void *unused);
+ int afterLightSleep(esp_sleep_wakeup_cause_t cause);
+#endif
protected:
static RotaryEncoderImpl *interruptInstance;
@@ -23,6 +29,21 @@ class RotaryEncoderImpl : public InputPollable
input_broker_event eventPressed = INPUT_BROKER_NONE;
RotaryEncoder *rotary;
+
+ private:
+#ifdef ARCH_ESP32
+ bool isFirstInit;
+#endif
+ void detachRotaryEncoderInterrupts();
+ void attachRotaryEncoderInterrupts();
+
+#ifdef ARCH_ESP32
+ // Get notified when lightsleep begins and ends
+ CallbackObserver lsObserver =
+ CallbackObserver(this, &RotaryEncoderImpl::beforeLightSleep);
+ CallbackObserver lsEndObserver =
+ CallbackObserver(this, &RotaryEncoderImpl::afterLightSleep);
+#endif
};
extern RotaryEncoderImpl *rotaryEncoderImpl;
diff --git a/src/input/RotaryEncoderInterruptImpl1.cpp b/src/input/RotaryEncoderInterruptImpl1.cpp
index 12cbc36fb..1da2ea008 100644
--- a/src/input/RotaryEncoderInterruptImpl1.cpp
+++ b/src/input/RotaryEncoderInterruptImpl1.cpp
@@ -27,7 +27,9 @@ bool RotaryEncoderInterruptImpl1::init()
RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB,
RotaryEncoderInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this);
+#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
+#endif
return true;
}
diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp
index d2025c192..bbd07e199 100644
--- a/src/input/TrackballInterruptBase.cpp
+++ b/src/input/TrackballInterruptBase.cpp
@@ -45,7 +45,9 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
LOG_DEBUG("Trackball GPIO initialized - UP:%d DOWN:%d LEFT:%d RIGHT:%d PRESS:%d", this->_pinUp, this->_pinDown,
this->_pinLeft, this->_pinRight, pinPress);
+#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
+#endif
this->setInterval(100);
}
diff --git a/src/input/UpDownInterruptImpl1.cpp b/src/input/UpDownInterruptImpl1.cpp
index 9b0b1f39e..906dcd2a8 100644
--- a/src/input/UpDownInterruptImpl1.cpp
+++ b/src/input/UpDownInterruptImpl1.cpp
@@ -29,7 +29,9 @@ bool UpDownInterruptImpl1::init()
eventDownLong, UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp,
UpDownInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this);
+#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
+#endif
return true;
}
diff --git a/src/main.cpp b/src/main.cpp
index f8d89e1ba..245f06e05 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -439,6 +439,13 @@ void setup()
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
+#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
+#ifndef SENSECAP_INDICATOR
+ // use PSRAM for malloc calls > 256 bytes
+ heap_caps_malloc_extmem_enable(256);
+#endif
+#endif
+
#if defined(DEBUG_MUTE) && defined(DEBUG_PORT)
DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n");
DEBUG_PORT.printf("Version %s for %s from %s\r\n", optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
@@ -1451,8 +1458,10 @@ void setup()
#endif
#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2)
+#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
#endif
+#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
// Start web server thread.
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 032be241b..b7459abe0 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -124,6 +124,10 @@ void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
}
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE && iface && nodeDB &&
+ nodeDB->isFromOrToFavoritedNode(*p)) {
+ iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
+ }
}
bool FloodingRouter::isRebroadcaster()
diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp
index c5748a560..83b64a873 100644
--- a/src/mesh/MeshModule.cpp
+++ b/src/mesh/MeshModule.cpp
@@ -195,7 +195,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
// but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs
// bad.
routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel,
- routingModule->getHopLimitForResponse(mp.hop_start, mp.hop_limit));
+ routingModule->getHopLimitForResponse(mp));
}
}
@@ -235,7 +235,7 @@ void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to)
assert(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // Should already be set by now
p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0
p->channel = to.channel; // Use the same channel that the request came in on
- p->hop_limit = routingModule->getHopLimitForResponse(to.hop_start, to.hop_limit);
+ p->hop_limit = routingModule->getHopLimitForResponse(to);
// No need for an ack if we are just delivering locally (it just generates an ignored ack)
p->want_ack = (to.from != 0) ? to.want_ack : false;
diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h
index eda3f8881..e7178bcfe 100644
--- a/src/mesh/MeshModule.h
+++ b/src/mesh/MeshModule.h
@@ -225,4 +225,4 @@ class MeshModule
/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet
* This ensures that if the request packet was sent reliably, the reply is sent that way as well.
*/
-void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to);
\ No newline at end of file
+void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to);
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index 1b2af082d..368642bf8 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -93,11 +93,8 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) {
if (airTime->isTxAllowedChannelUtil(true)) {
- // Hops used by the request. If somebody in between running modified firmware modified it, ignore it
- auto hopStart = mp->hop_start;
- auto hopLimit = mp->hop_limit;
- uint8_t hopsUsed = hopStart < hopLimit ? config.lora.hop_limit : hopStart - hopLimit;
- if (hopsUsed > config.lora.hop_limit + 2) {
+ const int8_t hopsUsed = getHopsAway(*mp, config.lora.hop_limit);
+ if (hopsUsed > (int32_t)(config.lora.hop_limit + 2)) {
LOG_DEBUG("Skip send NodeInfo: %d hops away is too far away", hopsUsed);
} else {
LOG_INFO("Heard new node on ch. %d, send NodeInfo and ask for response", mp->channel);
@@ -276,6 +273,10 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
if (nodeDB->hasValidPosition(node)) {
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
if (positionModule) {
+ if (!config.position.fixed_position && !nodeDB->hasLocalPositionSinceBoot()) {
+ LOG_DEBUG("Skip position ping; no fresh position since boot");
+ return false;
+ }
LOG_INFO("Send position ping to 0x%x, wantReplies=%d, channel=%d", dest, wantReplies, node->channel);
positionModule->sendOurPosition(dest, wantReplies, node->channel);
return true;
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index afdb4d096..5230e5b85 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -64,7 +64,7 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
perhapsRebroadcast(p);
}
} else {
- bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
+ bool isRepeated = getHopsAway(*p) == 0;
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
if (isRepeated) {
if (!findInTxQueue(p->from, p->id)) {
@@ -101,8 +101,7 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
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 ((weWereRelayer && wasAlreadyRelayer) || (getHopsAway(*p) == 0 && weWereSoleRelayer)) {
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);
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index d3000c500..3c408f01f 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -805,11 +805,16 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.output_ms = 500;
moduleConfig.external_notification.nag_timeout = 2;
#endif
-#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312)
- // Default to RAK led pin 2 (blue)
+#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \
+ defined(ELECROW_ThinkNode_M6)
+ // Default to PIN_LED2 for external notification output (LED color depends on device variant)
moduleConfig.external_notification.enabled = true;
moduleConfig.external_notification.output = PIN_LED2;
+#if defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3)
+ moduleConfig.external_notification.active = false;
+#else
moduleConfig.external_notification.active = true;
+#endif
moduleConfig.external_notification.alert_message = true;
moduleConfig.external_notification.output_ms = 1000;
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
@@ -1039,6 +1044,7 @@ void NodeDB::clearLocalPosition()
node->position.altitude = 0;
node->position.time = 0;
setLocalPosition(meshtastic_Position_init_default);
+ localPositionUpdatedSinceBoot = false;
}
void NodeDB::cleanupMeshDB()
@@ -1543,6 +1549,23 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
return delta;
}
+int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown)
+{
+ // Firmware prior to 2.3.0 (585805c) lacked a hop_start field. Firmware version 2.5.0 (bf34329) introduced a
+ // bitfield that is always present. Use the presence of the bitfield to determine if the origin's firmware
+ // version is guaranteed to have hop_start populated. Note that this can only be done for decoded packets as
+ // the bitfield is encrypted under the channel encryption key. For encrypted packets, this returns
+ // defaultIfUnknown when hop_start is 0.
+ if (p.hop_start == 0 && !(p.which_payload_variant == meshtastic_MeshPacket_decoded_tag && p.decoded.has_bitfield))
+ return defaultIfUnknown; // Cannot reliably determine the number of hops.
+
+ // Guard against invalid values.
+ if (p.hop_start < p.hop_limit)
+ return defaultIfUnknown;
+
+ return p.hop_start - p.hop_limit;
+}
+
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline
size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
@@ -1795,9 +1818,10 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
- if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) {
+ const int8_t hopsAway = getHopsAway(mp);
+ if (hopsAway >= 0) {
info->has_hops_away = true;
- info->hops_away = mp.hop_start - mp.hop_limit;
+ info->hops_away = hopsAway;
}
sortMeshDB();
}
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index 306acc0a5..817e31617 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -110,6 +110,10 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
/// Given a packet, return how many seconds in the past (vs now) it was received
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
+/// Given a packet, return the number of hops used to reach this node.
+/// Returns defaultIfUnknown if the number of hops couldn't be determined.
+int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown = -1);
+
enum LoadFileResult {
// Successfully opened the file
LOAD_SUCCESS = 1,
@@ -279,9 +283,13 @@ class NodeDB
LOG_DEBUG("Set local position: lat=%i lon=%i time=%u timestamp=%u", position.latitude_i, position.longitude_i,
position.time, position.timestamp);
localPosition = position;
+ if (position.latitude_i != 0 || position.longitude_i != 0) {
+ localPositionUpdatedSinceBoot = true;
+ }
}
bool hasValidPosition(const meshtastic_NodeInfoLite *n);
+ bool hasLocalPositionSinceBoot() const { return localPositionUpdatedSinceBoot; }
#if !defined(MESHTASTIC_EXCLUDE_PKI)
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
@@ -301,6 +309,7 @@ class NodeDB
private:
bool duplicateWarned = false;
+ bool localPositionUpdatedSinceBoot = false;
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually
uint32_t lastSort = 0; // When last sorted the nodeDB
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 3c0da4494..f7daf1122 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -296,11 +296,6 @@ bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
return true;
}
- // If we are a CLIENT_BASE and the packet is from or to a favorited node, we should rebroadcast early
- if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
- return nodeDB->isFromOrToFavoritedNode(*p);
- }
-
return false;
}
@@ -503,6 +498,11 @@ void RadioInterface::applyModemConfig()
cr = 5;
sf = 10;
break;
+ case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
+ bw = (myRegion->wideLora) ? 1625.0 : 500;
+ cr = 8;
+ sf = 11;
+ break;
default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal.
bw = (myRegion->wideLora) ? 812.5 : 250;
cr = 5;
@@ -539,13 +539,26 @@ void RadioInterface::applyModemConfig()
}
if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) {
- static const char *err_string = "Regional frequency range is smaller than bandwidth. Fall back to default preset";
- LOG_ERROR(err_string);
+ const float regionSpanKHz = (myRegion->freqEnd - myRegion->freqStart) * 1000.0f;
+ const float requestedBwKHz = bw;
+ const bool isWideRequest = requestedBwKHz >= 499.5f; // treat as 500 kHz preset
+ const char *presetName =
+ DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset);
+
+ char err_string[160];
+ if (isWideRequest) {
+ snprintf(err_string, sizeof(err_string), "%s region too narrow for 500kHz preset (%s). Falling back to LongFast.",
+ myRegion->name, presetName);
+ } else {
+ snprintf(err_string, sizeof(err_string), "%s region span %.0fkHz < requested %.0fkHz. Falling back to LongFast.",
+ myRegion->name, regionSpanKHz, requestedBwKHz);
+ }
+ LOG_ERROR("%s", err_string);
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_ERROR;
- sprintf(cn->message, err_string);
+ snprintf(cn->message, sizeof(cn->message), "%s", err_string);
service->sendClientNotification(cn);
// Set to default modem preset
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index 00066a7a3..2b9b17183 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -1,6 +1,7 @@
#include "ReliableRouter.h"
#include "Default.h"
#include "MeshTypes.h"
+#include "NodeDB.h"
#include "configuration.h"
#include "memGet.h"
#include "mesh-pb-constants.h"
@@ -108,12 +109,12 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
// 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);
+ routingModule->getHopLimitForResponse(*p), true);
} else if (!p->decoded.request_id && !p->decoded.reply_id) {
// If it's not an ACK or a reply, send an ACK.
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) {
+ routingModule->getHopLimitForResponse(*p));
+ } else if ((getHopsAway(*p) == 0) || 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.
@@ -123,11 +124,11 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
- routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
+ routingModule->getHopLimitForResponse(*p));
} else {
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
- routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
+ routingModule->getHopLimitForResponse(*p));
}
} else if (p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum()) && p->hop_limit > 0) {
// No wantAck, but we need to ACK with hop limit of 0 if we were the next hop to stop their retransmissions
@@ -150,7 +151,9 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
PacketId nakId = (c && c->error_reason != meshtastic_Routing_Error_NONE) ? p->decoded.request_id : 0;
// We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records
- if (ackId || nakId) {
+ if ((ackId || nakId) &&
+ // Implicit ACKs from MQTT should not stop retransmissions
+ !(isFromUs(p) && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MQTT)) {
LOG_DEBUG("Received a %s for 0x%x, stopping retransmissions", ackId ? "ACK" : "NAK", ackId);
if (ackId) {
stopRetransmission(p->to, ackId);
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 05f47d7f4..92bf2b420 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -37,8 +37,8 @@
static MemoryDynamic dynamicPool;
Allocator &packetPool = dynamicPool;
-#elif defined(ARCH_STM32WL)
-// On STM32 there isn't enough heap left over for the rest of the firmware if we allocate this statically.
+#elif defined(ARCH_STM32WL) || defined(BOARD_HAS_PSRAM)
+// On STM32 and boards with PSRAM, there isn't enough heap left over for the rest of the firmware if we allocate this statically.
// For now, make it dynamic again.
#define MAX_PACKETS \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
@@ -81,8 +81,7 @@ Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRA
bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
{
// First hop MUST always decrement to prevent retry issues
- bool isFirstHop = (p->hop_start != 0 && p->hop_start == p->hop_limit);
- if (isFirstHop) {
+ if (getHopsAway(*p) == 0) {
return true; // Always decrement on first hop
}
@@ -526,6 +525,10 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
#elif ARCH_PORTDUINO
if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
+ } else if (portduino_config.JSONFilename != "") {
+ if (portduino_config.JSONFilter == (_meshtastic_PortNum)0 || portduino_config.JSONFilter == p->decoded.portnum) {
+ JSONFile << MeshPacketSerializer::JsonSerialize(p, false) << std::endl;
+ }
}
#endif
return DecodeState::DECODE_SUCCESS;
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 327568316..d4ef5bee4 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -293,7 +293,8 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode {
typedef enum _meshtastic_Config_LoRaConfig_ModemPreset {
/* Long Range - Fast */
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST = 0,
- /* Long Range - Slow */
+ /* Long Range - Slow
+ Deprecated in 2.7: Unpopular slow preset. */
meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW = 1,
/* Very Long Range - Slow
Deprecated in 2.5: Works only with txco and is unusably slow */
@@ -311,7 +312,10 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset {
/* Short Range - Turbo
This is the fastest preset and the only one with 500kHz bandwidth.
It is not legal to use in all regions due to this wider bandwidth. */
- meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO = 8
+ meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO = 8,
+ /* Long Range - Turbo
+ This preset performs similarly to LongFast, but with 500Khz bandwidth. */
+ meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO = 9
} meshtastic_Config_LoRaConfig_ModemPreset;
typedef enum _meshtastic_Config_BluetoothConfig_PairingMode {
@@ -689,8 +693,8 @@ extern "C" {
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_BR_902+1))
#define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST
-#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO
-#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO+1))
+#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO
+#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO+1))
#define _meshtastic_Config_BluetoothConfig_PairingMode_MIN meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN
#define _meshtastic_Config_BluetoothConfig_PairingMode_MAX meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index aa510a86d..5f0c27fff 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -417,6 +417,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
}
case meshtastic_AdminMessage_enter_dfu_mode_request_tag: {
LOG_INFO("Client requesting to enter DFU mode");
+#if HAS_SCREEN
+ IF_SCREEN(screen->showSimpleBanner("Device is rebooting\ninto DFU mode.", 0));
+#endif
#if defined(ARCH_NRF52) || defined(ARCH_RP2040)
enterDfuMode();
#endif
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index f918d630f..63392f7e4 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -217,7 +217,7 @@ void setupModules()
}
#endif // HAS_BUTTON
#if ARCH_PORTDUINO
- if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+ if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") {
seesawRotary = new SeesawRotary("SeesawRotary");
if (!seesawRotary->init()) {
delete seesawRotary;
diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp
index 936a7b44a..2cd8ec5ed 100644
--- a/src/modules/NeighborInfoModule.cpp
+++ b/src/modules/NeighborInfoModule.cpp
@@ -170,7 +170,7 @@ bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
} else {
LOG_DEBUG(" Ignoring dummy neighbor info packet (single neighbor with nodeId 0, snr 0)");
}
- } else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) {
+ } else if (getHopsAway(mp) == 0) {
LOG_DEBUG("Get or create neighbor: %u with snr %f", mp.from, mp.rx_snr);
// If the hopLimit is the same as hopStart, then it is a neighbor
getOrCreateNeighbor(mp.from, mp.from, 0,
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index aaab019d6..7db8b66cc 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -7,17 +7,41 @@
#include "configuration.h"
#include "main.h"
#include
+#include
+
+#ifndef USERPREFS_NODEINFO_REPLY_SUPPRESS_SECS
+#define USERPREFS_NODEINFO_REPLY_SUPPRESS_SECS (12 * 60 * 60)
+#endif
NodeInfoModule *nodeInfoModule;
+static constexpr uint32_t NodeInfoReplySuppressSeconds = USERPREFS_NODEINFO_REPLY_SUPPRESS_SECS;
+
bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *pptr)
{
+ suppressReplyForCurrentRequest = false;
+
if (mp.from == nodeDB->getNodeNum()) {
LOG_WARN("Ignoring packet supposed to be from our own node: %08x", mp.from);
return false;
}
auto p = *pptr;
+
+ if (mp.decoded.want_response) {
+ const NodeNum sender = getFrom(&mp);
+ const uint32_t now = mp.rx_time ? mp.rx_time : getTime();
+ auto it = lastNodeInfoSeen.find(sender);
+ if (it != lastNodeInfoSeen.end()) {
+ uint32_t sinceLast = now >= it->second ? now - it->second : 0;
+ if (sinceLast < NodeInfoReplySuppressSeconds) {
+ suppressReplyForCurrentRequest = true;
+ }
+ }
+ lastNodeInfoSeen[sender] = now;
+ pruneLastNodeInfoCache();
+ }
+
if (p.is_licensed != owner.is_licensed) {
LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
return true;
@@ -42,6 +66,8 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
service->sendToPhone(packetCopy);
}
+ pruneLastNodeInfoCache();
+
// LOG_DEBUG("did handleReceived");
return false; // Let others look at this message also if they want
}
@@ -68,9 +94,11 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
if (p) { // Check whether we didn't ignore it
p->to = dest;
- p->decoded.want_response = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
+ bool requestWantResponse = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
wantReplies;
+
+ p->decoded.want_response = requestWantResponse;
if (_shorterTimeout)
p->priority = meshtastic_MeshPacket_Priority_DEFAULT;
else
@@ -89,6 +117,13 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
meshtastic_MeshPacket *NodeInfoModule::allocReply()
{
+ if (suppressReplyForCurrentRequest) {
+ LOG_DEBUG("Skip send NodeInfo since we heard the requester <12h ago");
+ ignoreRequest = true;
+ suppressReplyForCurrentRequest = false;
+ return NULL;
+ }
+
if (!airTime->isTxAllowedChannelUtil(false)) {
ignoreRequest = true; // Mark it as ignored for MeshModule
LOG_DEBUG("Skip send NodeInfo > 40%% ch. util");
@@ -125,6 +160,29 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
}
}
+void NodeInfoModule::pruneLastNodeInfoCache()
+{
+ if (!nodeDB || !nodeDB->meshNodes)
+ return;
+
+ const size_t maxEntries = nodeDB->meshNodes->size();
+
+ for (auto it = lastNodeInfoSeen.begin(); it != lastNodeInfoSeen.end();) {
+ if (!nodeDB->getMeshNode(it->first)) {
+ it = lastNodeInfoSeen.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ while (!lastNodeInfoSeen.empty() && lastNodeInfoSeen.size() > maxEntries) {
+ auto oldestIt = std::min_element(lastNodeInfoSeen.begin(), lastNodeInfoSeen.end(),
+ [](const std::pair &lhs,
+ const std::pair &rhs) { return lhs.second < rhs.second; });
+ lastNodeInfoSeen.erase(oldestIt);
+ }
+}
+
NodeInfoModule::NodeInfoModule()
: ProtobufModule("nodeinfo", meshtastic_PortNum_NODEINFO_APP, &meshtastic_User_msg), concurrency::OSThread("NodeInfo")
{
diff --git a/src/modules/NodeInfoModule.h b/src/modules/NodeInfoModule.h
index 572b81700..d16fbeac2 100644
--- a/src/modules/NodeInfoModule.h
+++ b/src/modules/NodeInfoModule.h
@@ -1,5 +1,6 @@
#pragma once
#include "ProtobufModule.h"
+#include