mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-15 23:32:34 +00:00
Compare commits
108 Commits
d772cd4431
...
thinknode-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d5dbdbdbf | ||
|
|
9cf369c5d0 | ||
|
|
2ca03fbf4b | ||
|
|
567b8ea1c2 | ||
|
|
d39d1917ad | ||
|
|
b202559d37 | ||
|
|
85ea22ac38 | ||
|
|
15257b017c | ||
|
|
59864dd09d | ||
|
|
edcdb2dcb2 | ||
|
|
ef4cb2abfb | ||
|
|
c34f94abda | ||
|
|
a8d1a90e16 | ||
|
|
501c296e75 | ||
|
|
ec5e79585b | ||
|
|
438e170b03 | ||
|
|
43e0c35466 | ||
|
|
6e3be132f2 | ||
|
|
0aa11d810c | ||
|
|
4df6627ab1 | ||
|
|
e9590003f4 | ||
|
|
1c0c6b2736 | ||
|
|
602945f66b | ||
|
|
b86827967e | ||
|
|
b707001873 | ||
|
|
85afd706fd | ||
|
|
e76013fb60 | ||
|
|
b25797e1b3 | ||
|
|
bdb3fb1477 | ||
|
|
7eca061f01 | ||
|
|
4d86bbafe6 | ||
|
|
112b294ef6 | ||
|
|
5ba04ade2d | ||
|
|
69db3bd11c | ||
|
|
45bf2468a9 | ||
|
|
ce2e08e0d8 | ||
|
|
a579a9d011 | ||
|
|
6b55ec6603 | ||
|
|
f2400c9dc6 | ||
|
|
0a13bcaabf | ||
|
|
3ed831b8a3 | ||
|
|
cf716fe5ef | ||
|
|
597fa0b382 | ||
|
|
b5b9dc310f | ||
|
|
a7796fc7b4 | ||
|
|
bca0e1abde | ||
|
|
c46abe125c | ||
|
|
16b1280804 | ||
|
|
d00fda2f4d | ||
|
|
756efa7f00 | ||
|
|
0dfa11a909 | ||
|
|
c330bfe848 | ||
|
|
7d3e529b2f | ||
|
|
dd51de85f3 | ||
|
|
580fa292ac | ||
|
|
664d17c519 | ||
|
|
95d3ecb239 | ||
|
|
799cf0e8b3 | ||
|
|
35fa418739 | ||
|
|
585d9d36a8 | ||
|
|
b682ab3967 | ||
|
|
49b9d5151d | ||
|
|
3f8707cafe | ||
|
|
39780656ef | ||
|
|
153cf65214 | ||
|
|
07d354fa02 | ||
|
|
f4e93b4a2d | ||
|
|
18c4956aba | ||
|
|
15ee1c2819 | ||
|
|
f4ff210311 | ||
|
|
26747038bb | ||
|
|
8e082686a3 | ||
|
|
871986d183 | ||
|
|
821d8aa15d | ||
|
|
c4656dacf7 | ||
|
|
64d92679d0 | ||
|
|
b5aa16bade | ||
|
|
c740550d16 | ||
|
|
cb3ce1b1a8 | ||
|
|
2ad52812c0 | ||
|
|
5b9563a357 | ||
|
|
126954c2ed | ||
|
|
f6eede8597 | ||
|
|
6f2241751e | ||
|
|
1d4134c08c | ||
|
|
ffb168be00 | ||
|
|
27dc3be14a | ||
|
|
f2a63faddd | ||
|
|
d2403437ff | ||
|
|
05c176c16a | ||
|
|
7afc6ef833 | ||
|
|
68e739359f | ||
|
|
2357ea0042 | ||
|
|
cbdbaf62f1 | ||
|
|
e1c259ae32 | ||
|
|
b4dea63f44 | ||
|
|
30022c9377 | ||
|
|
0283e0658b | ||
|
|
4f142e6766 | ||
|
|
af8407aca9 | ||
|
|
e5d67310d6 | ||
|
|
47df9d4a79 | ||
|
|
4df79374b0 | ||
|
|
0bfc342b48 | ||
|
|
d9905f3c31 | ||
|
|
ee2ed0a8fb | ||
|
|
acab814b6f | ||
|
|
073c35c782 |
@@ -1,7 +1,7 @@
|
||||
# trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
|
||||
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
||||
FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12
|
||||
FROM mcr.microsoft.com/devcontainers/cpp:2-debian-13
|
||||
|
||||
USER root
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/python:1": {
|
||||
"installTools": true,
|
||||
"version": "3.13"
|
||||
"version": "3.14"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
|
||||
2
.github/actions/build-variant/action.yml
vendored
2
.github/actions/build-variant/action.yml
vendored
@@ -100,7 +100,7 @@ runs:
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
|
||||
2
.github/workflows/build_debian_src.yml
vendored
2
.github/workflows/build_debian_src.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
PKG_VERSION: ${{ steps.version.outputs.deb }}
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
overwrite: true
|
||||
|
||||
2
.github/workflows/build_firmware.yml
vendored
2
.github/workflows/build_firmware.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
id: upload
|
||||
with:
|
||||
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
|
||||
|
||||
8
.github/workflows/build_one_arch.yml
vendored
8
.github/workflows/build_one_arch.yml
vendored
@@ -113,7 +113,7 @@ jobs:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: ./
|
||||
pattern: firmware-${{inputs.arch}}-*
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
|
||||
8
.github/workflows/build_one_target.yml
vendored
8
.github/workflows/build_one_target.yml
vendored
@@ -119,7 +119,7 @@ jobs:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: ./
|
||||
pattern: firmware-*-*
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-*-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -167,7 +167,7 @@ jobs:
|
||||
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
|
||||
18
.github/workflows/main_matrix.yml
vendored
18
.github/workflows/main_matrix.yml
vendored
@@ -168,7 +168,7 @@ jobs:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: ./
|
||||
pattern: firmware-${{matrix.arch}}-*
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -216,7 +216,7 @@ jobs:
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
@@ -261,14 +261,14 @@ jobs:
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
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
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -318,7 +318,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -335,7 +335,7 @@ jobs:
|
||||
- name: Zip firmware
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
@@ -373,7 +373,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
|
||||
18
.github/workflows/merge_queue.yml
vendored
18
.github/workflows/merge_queue.yml
vendored
@@ -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@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
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@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
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@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
@@ -240,14 +240,14 @@ jobs:
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
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
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -297,7 +297,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -314,7 +314,7 @@ jobs:
|
||||
- name: Zip firmware
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
@@ -352,7 +352,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/package_obs.yml
vendored
2
.github/workflows/package_obs.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/package_pio_deps.yml
vendored
2
.github/workflows/package_pio_deps.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
PLATFORMIO_CORE_DIR: pio/core
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
|
||||
overwrite: true
|
||||
|
||||
2
.github/workflows/package_ppa.yml
vendored
2
.github/workflows/package_ppa.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/pr_tests.yml
vendored
2
.github/workflows/pr_tests.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Download test artifacts
|
||||
if: needs.native-tests.result != 'skipped'
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/sec_sast_semgrep_cron.yml
vendored
2
.github/workflows/sec_sast_semgrep_cron.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
# step 3
|
||||
- name: save report as pipeline artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: report.sarif
|
||||
overwrite: true
|
||||
|
||||
14
.github/workflows/test_native.yml
vendored
14
.github/workflows/test_native.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Save coverage information
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
if: always() # run this step even if previous step failed
|
||||
with:
|
||||
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}.zip
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
|
||||
- name: Save test results
|
||||
if: always() # run this step even if previous step failed
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||
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@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
if: always() # run this step even if previous step failed
|
||||
with:
|
||||
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}.zip
|
||||
@@ -137,20 +137,20 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download test artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
|
||||
- name: Test Report
|
||||
uses: dorny/test-reporter@v2.1.1
|
||||
uses: dorny/test-reporter@v2.2.0
|
||||
with:
|
||||
name: PlatformIO Tests
|
||||
path: testreport.xml
|
||||
reporter: java-junit
|
||||
|
||||
- name: Download coverage artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
|
||||
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@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: code-coverage-report-${{ steps.version.outputs.long }}.zip
|
||||
path: code-coverage-report
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
@@ -8,15 +8,15 @@ plugins:
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.483
|
||||
- renovate@41.148.2
|
||||
- checkov@3.2.492
|
||||
- renovate@42.5.4
|
||||
- prettier@3.6.2
|
||||
- trufflehog@3.90.8
|
||||
- trufflehog@3.90.13
|
||||
- yamllint@1.37.1
|
||||
- bandit@1.8.6
|
||||
- trivy@0.67.2
|
||||
- taplo@0.10.0
|
||||
- ruff@0.14.0
|
||||
- ruff@0.14.4
|
||||
- isort@7.0.0
|
||||
- markdownlint@0.45.0
|
||||
- oxipng@9.1.5
|
||||
@@ -26,9 +26,9 @@ lint:
|
||||
- hadolint@2.14.0
|
||||
- shfmt@3.6.0
|
||||
- shellcheck@0.11.0
|
||||
- black@25.9.0
|
||||
- black@25.11.0
|
||||
- git-diff-check
|
||||
- gitleaks@8.28.0
|
||||
- gitleaks@8.29.0
|
||||
- clang-format@16.0.3
|
||||
ignore:
|
||||
- linters: [ALL]
|
||||
|
||||
@@ -8,7 +8,8 @@ ARG PIO_ENV=native
|
||||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
|
||||
RUN apk --no-cache add \
|
||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
|
||||
libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
||||
libx11-dev libinput-dev libxkbcommon-dev \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
@@ -40,8 +41,8 @@ LABEL org.opencontainers.image.title="Meshtastic" \
|
||||
USER root
|
||||
|
||||
RUN apk --no-cache add \
|
||||
shadow libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
|
||||
libx11 libinput libxkbcommon \
|
||||
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
|
||||
i2c-tools libuv libx11 libinput libxkbcommon \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
[portduino_base]
|
||||
platform =
|
||||
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
|
||||
https://github.com/meshtastic/platform-native/archive/d3f6e339534233c7217818867368767590ce549e.zip
|
||||
https://github.com/meshtastic/platform-native/archive/f566d364204416cdbf298e349213f7d551f793d9.zip
|
||||
framework = arduino
|
||||
|
||||
build_src_filter =
|
||||
|
||||
@@ -15,12 +15,12 @@ SET "LOGCOUNTER=0"
|
||||
SET "BPS_RESET=0"
|
||||
|
||||
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
||||
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv"
|
||||
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv heltec-v4"
|
||||
SET "C3=esp32c3"
|
||||
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
|
||||
SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
|
||||
SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
|
||||
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv"
|
||||
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv heltec-v4"
|
||||
|
||||
GOTO getopts
|
||||
:help
|
||||
|
||||
@@ -31,21 +31,23 @@ MUIDB_8MB=(
|
||||
"seeed-sensecap-indicator"
|
||||
)
|
||||
BIGDB_16MB=(
|
||||
"t-deck"
|
||||
"mesh-tab"
|
||||
"t-energy-s3"
|
||||
"dreamcatcher"
|
||||
"ESP32-S3-Pico"
|
||||
"m5stack-cores3"
|
||||
"station-g2"
|
||||
"t-eth-elite"
|
||||
"tlora-pager"
|
||||
"t-watch-s3"
|
||||
"elecrow-adv"
|
||||
"ESP32-S3-Pico"
|
||||
"heltec-v4"
|
||||
"m5stack-cores3"
|
||||
"mesh-tab"
|
||||
"station-g2"
|
||||
"t-deck"
|
||||
"t-energy-s3"
|
||||
"t-eth-elite"
|
||||
"t-watch-s3"
|
||||
"tlora-pager"
|
||||
)
|
||||
S3_VARIANTS=(
|
||||
"s3"
|
||||
"-v3"
|
||||
"-v4"
|
||||
"t-deck"
|
||||
"wireless-paper"
|
||||
"wireless-tracker"
|
||||
|
||||
@@ -87,6 +87,12 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.7.15" date="2025-11-13">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.15</url>
|
||||
</release>
|
||||
<release version="2.7.14" date="2025-11-03">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.14</url>
|
||||
</release>
|
||||
<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>
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.6.6
|
||||
2.6.7
|
||||
12
debian/changelog
vendored
12
debian/changelog
vendored
@@ -1,3 +1,15 @@
|
||||
meshtasticd (2.7.15.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.15
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Thu, 13 Nov 2025 12:31:57 +0000
|
||||
|
||||
meshtasticd (2.7.14.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.14
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Mon, 03 Nov 2025 16:11:31 +0000
|
||||
|
||||
meshtasticd (2.7.13.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.13
|
||||
|
||||
1
debian/control
vendored
1
debian/control
vendored
@@ -3,6 +3,7 @@ Section: misc
|
||||
Priority: optional
|
||||
Maintainer: Austin Lane <vidplace7@gmail.com>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
libc6-dev (>= 2.38) | libbsd-dev,
|
||||
lsb-release,
|
||||
tar,
|
||||
gzip,
|
||||
|
||||
@@ -49,6 +49,13 @@ BuildRequires: pkgconfig(x11)
|
||||
BuildRequires: pkgconfig(libinput)
|
||||
BuildRequires: pkgconfig(xkbcommon-x11)
|
||||
|
||||
# libbsd is needed on older Fedora/RHEL to provide 'strlcpy'
|
||||
%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10
|
||||
BuildRequires: glibc-devel >= 2.38
|
||||
%else
|
||||
BuildRequires: pkgconfig(libbsd-overlay)
|
||||
%endif
|
||||
|
||||
Requires: systemd-udev
|
||||
|
||||
%description
|
||||
|
||||
@@ -62,7 +62,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/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.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
|
||||
@@ -120,7 +120,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/28167c67dfd13015a0b5eef1828f95fe8e3ab7c3.zip
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
[environmental_base]
|
||||
@@ -164,7 +164,7 @@ lib_deps =
|
||||
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
|
||||
mprograms/QMC5883LCompass@1.2.3
|
||||
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
|
||||
dfrobot/DFRobot_RTU@1.0.3
|
||||
dfrobot/DFRobot_RTU@1.0.6
|
||||
# renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master
|
||||
https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip
|
||||
# renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226
|
||||
@@ -176,7 +176,7 @@ lib_deps =
|
||||
# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
|
||||
adafruit/Adafruit LTR390 Library@1.1.2
|
||||
# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
|
||||
adafruit/Adafruit PCT2075@1.0.5
|
||||
adafruit/Adafruit PCT2075@1.0.6
|
||||
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
|
||||
dfrobot/DFRobot_BMM150@1.0.0
|
||||
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
|
||||
|
||||
Submodule protobufs updated: 38638f19f8...7654db2e2d
@@ -839,8 +839,11 @@ void Power::readPowerStatus()
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
|
||||
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(),
|
||||
powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
|
||||
if (millis() > lastLogTime + 50 * 1000) {
|
||||
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
|
||||
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
|
||||
lastLogTime = millis();
|
||||
}
|
||||
newStatus.notifyObservers(&powerStatus2);
|
||||
#ifdef DEBUG_HEAP
|
||||
if (lastheap != memGet.getFreeHeap()) {
|
||||
|
||||
@@ -50,6 +50,7 @@ void consolePrintf(const char *format, ...)
|
||||
|
||||
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
|
||||
{
|
||||
api_type = TYPE_SERIAL;
|
||||
assert(!console);
|
||||
console = this;
|
||||
canWrite = false; // We don't send packets to our port until it has talked to us first
|
||||
|
||||
@@ -126,6 +126,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#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
|
||||
|
||||
#ifdef RAK13302
|
||||
#define NUM_PA_POINTS 22
|
||||
#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8
|
||||
#endif
|
||||
|
||||
// Default system gain to 0 if not defined
|
||||
#ifndef TX_GAIN_LORA
|
||||
#define TX_GAIN_LORA 0
|
||||
@@ -245,6 +250,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// -----------------------------------------------------------------------------
|
||||
#define FT6336U_ADDR 0x48
|
||||
#define CST328_ADDR 0x1A
|
||||
#define CHSC6X_ADDR 0x2E
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
|
||||
|
||||
@@ -82,7 +82,10 @@ class ScanI2C
|
||||
BHI260AP,
|
||||
BMM150,
|
||||
TSL2561,
|
||||
DRV2605
|
||||
DRV2605,
|
||||
BH1750,
|
||||
DA217,
|
||||
CHSC6X
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -485,7 +485,26 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(LTR553ALS_ADDR, LTR553ALS, "LTR553ALS", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address);
|
||||
case LTR553ALS_ADDR:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
|
||||
if (registerValue == 0x92) { // LTR553ALS Part ID
|
||||
type = LTR553ALS;
|
||||
logFoundDevice("LTR553ALS", (uint8_t)addr.address);
|
||||
} else {
|
||||
// Test BH1750 - send power on command
|
||||
i2cBus->beginTransmission(addr.address);
|
||||
i2cBus->write(0x01); // Power On command
|
||||
uint8_t bh1750_error = i2cBus->endTransmission();
|
||||
if (bh1750_error == 0) {
|
||||
type = BH1750;
|
||||
logFoundDevice("BH1750", (uint8_t)addr.address);
|
||||
} else {
|
||||
LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
|
||||
|
||||
@@ -1659,8 +1659,12 @@ bool GPS::lookForLocation()
|
||||
|
||||
#ifndef TINYGPS_OPTION_NO_STATISTICS
|
||||
if (reader.failedChecksum() > lastChecksumFailCount) {
|
||||
LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
|
||||
reader.failedChecksum());
|
||||
// In a GPS_DEBUG build we want to log all of these. In production, we only care if there are many of them.
|
||||
#ifndef GPS_DEBUG
|
||||
if (reader.failedChecksum() > 4)
|
||||
#endif
|
||||
LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
|
||||
reader.failedChecksum());
|
||||
lastChecksumFailCount = reader.failedChecksum();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -443,7 +443,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
if (uiconfig.screen_brightness == 1)
|
||||
digitalWrite(PIN_EINK_EN, HIGH);
|
||||
#elif defined(PCA_PIN_EINK_EN)
|
||||
if (uiconfig.screen_brightness == 1)
|
||||
if (uiconfig.screen_brightness > 0)
|
||||
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "MeshService.h"
|
||||
#include "RTC.h"
|
||||
#include "draw/NodeListRenderer.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "graphics/draw/UIRenderer.h"
|
||||
#include "main.h"
|
||||
#include "meshtastic/config.pb.h"
|
||||
@@ -396,6 +400,43 @@ const int *getTextPositions(OLEDDisplay *display)
|
||||
return textPositions;
|
||||
}
|
||||
|
||||
// *************************
|
||||
// * Common Footer Drawing *
|
||||
// *************************
|
||||
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
|
||||
{
|
||||
bool drawConnectionState = false;
|
||||
if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI ||
|
||||
service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET ||
|
||||
service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) {
|
||||
drawConnectionState = true;
|
||||
}
|
||||
|
||||
if (drawConnectionState) {
|
||||
if (isHighResolution) {
|
||||
const int scale = 2;
|
||||
const int bytesPerRow = (connection_icon_width + 7) / 8;
|
||||
int iconX = 0;
|
||||
int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
|
||||
|
||||
for (int yy = 0; yy < connection_icon_height; ++yy) {
|
||||
const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
|
||||
for (int xx = 0; xx < connection_icon_width; ++xx) {
|
||||
const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3));
|
||||
const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first
|
||||
if (byteVal & bitMask) {
|
||||
display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
|
||||
connection_icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isAllowedPunctuation(char c)
|
||||
{
|
||||
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
|
||||
@@ -423,3 +464,4 @@ std::string sanitizeString(const std::string &input)
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
#endif
|
||||
@@ -52,6 +52,9 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
|
||||
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
|
||||
bool show_date = false);
|
||||
|
||||
// Shared battery/time/mail header
|
||||
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y);
|
||||
|
||||
const int *getTextPositions(OLEDDisplay *display);
|
||||
|
||||
bool isAllowedPunctuation(char c);
|
||||
|
||||
@@ -422,7 +422,57 @@ static LGFX *tft = nullptr;
|
||||
|
||||
#elif defined(ST7789_CS)
|
||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||||
#ifdef HELTEC_V4_TFT
|
||||
#include "chsc6x.h"
|
||||
#include "lgfx/v1/Touch.hpp"
|
||||
namespace lgfx
|
||||
{
|
||||
inline namespace v1
|
||||
{
|
||||
class TOUCH_CHSC6X : public ITouch
|
||||
{
|
||||
public:
|
||||
TOUCH_CHSC6X(void)
|
||||
{
|
||||
_cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
|
||||
_cfg.x_min = 0;
|
||||
_cfg.x_max = 240;
|
||||
_cfg.y_min = 0;
|
||||
_cfg.y_max = 320;
|
||||
};
|
||||
|
||||
bool init(void) override
|
||||
{
|
||||
if (chsc6xTouch == nullptr) {
|
||||
chsc6xTouch = new chsc6x(&Wire1, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
|
||||
}
|
||||
chsc6xTouch->chsc6x_init();
|
||||
return true;
|
||||
};
|
||||
|
||||
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override
|
||||
{
|
||||
uint16_t raw_x, raw_y;
|
||||
if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y) == 0) {
|
||||
tp[0].x = 320 - 1 - raw_y;
|
||||
tp[0].y = 240 - 1 - raw_x;
|
||||
tp[0].size = 1;
|
||||
tp[0].id = 1;
|
||||
return 1;
|
||||
}
|
||||
tp[0].size = 0;
|
||||
return 0;
|
||||
};
|
||||
|
||||
void wakeup(void) override{};
|
||||
void sleep(void) override{};
|
||||
|
||||
private:
|
||||
chsc6x *chsc6xTouch = nullptr;
|
||||
};
|
||||
} // namespace v1
|
||||
} // namespace lgfx
|
||||
#endif
|
||||
class LGFX : public lgfx::LGFX_Device
|
||||
{
|
||||
lgfx::Panel_ST7789 _panel_instance;
|
||||
@@ -431,6 +481,10 @@ class LGFX : public lgfx::LGFX_Device
|
||||
#if HAS_TOUCHSCREEN
|
||||
#if defined(T_WATCH_S3) || defined(ELECROW)
|
||||
lgfx::Touch_FT5x06 _touch_instance;
|
||||
#elif defined(HELTEC_V4_TFT)
|
||||
lgfx::TOUCH_CHSC6X _touch_instance;
|
||||
#elif defined(HELTEC_V4_TFT)
|
||||
lgfx::TOUCH_CHSC6X _touch_instance;
|
||||
#else
|
||||
lgfx::Touch_GT911 _touch_instance;
|
||||
#endif
|
||||
@@ -464,9 +518,9 @@ class LGFX : public lgfx::LGFX_Device
|
||||
{ // Set the display panel control.
|
||||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||||
|
||||
cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
|
||||
cfg.pin_rst = -1; // Pin number where RST is connected (-1 = disable)
|
||||
cfg.pin_busy = -1; // Pin number where BUSY is connected (-1 = disable)
|
||||
cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
|
||||
cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable)
|
||||
cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||||
|
||||
// The following setting values are general initial values for each panel, so please comment out any
|
||||
// unknown items and try them.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "VirtualKeyboard.h"
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "VirtualKeyboard.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
@@ -736,3 +737,4 @@ bool VirtualKeyboard::isTimedOut() const
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
#endif
|
||||
@@ -302,6 +302,8 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
||||
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
|
||||
secondString);
|
||||
#endif
|
||||
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
|
||||
@@ -516,6 +518,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
display->drawLine(centerX, centerY, secondX, secondY);
|
||||
#endif
|
||||
}
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
} // namespace ClockRenderer
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "CompassRenderer.h"
|
||||
#include "NodeDB.h"
|
||||
#include "UIRenderer.h"
|
||||
@@ -135,3 +137,4 @@ uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
|
||||
|
||||
} // namespace CompassRenderer
|
||||
} // namespace graphics
|
||||
#endif
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../Screen.h"
|
||||
#include "DebugRenderer.h"
|
||||
#include "FSCommon.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "Throttle.h"
|
||||
#include "UIRenderer.h"
|
||||
@@ -223,6 +224,8 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
|
||||
|
||||
display->drawString(x, getTextPositions(display)[line++], "URL: http://meshtastic.local");
|
||||
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
if (heartbeat)
|
||||
@@ -503,6 +506,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
||||
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
|
||||
chUtilPercentage);
|
||||
#endif
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// ****************************
|
||||
@@ -642,10 +646,9 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
||||
int textWidth = display->getStringWidth(appversionstr);
|
||||
int nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||
|
||||
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
|
||||
#if !defined(M5STACK_UNITC6L)
|
||||
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
|
||||
line += 1;
|
||||
display->drawString(nameX, getTextPositions(display)[line++], appversionstr);
|
||||
|
||||
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it
|
||||
char uptimeStr[32] = "";
|
||||
uint32_t uptime = millis() / 1000;
|
||||
uint32_t days = uptime / 86400;
|
||||
@@ -660,9 +663,41 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
|
||||
textWidth = display->getStringWidth(uptimeStr);
|
||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
|
||||
display->drawString(nameX, getTextPositions(display)[line++], uptimeStr);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show API state if the screen can show it
|
||||
char api_state[32] = "";
|
||||
const char *clientWord = nullptr;
|
||||
|
||||
// Determine if narrow or wide screen
|
||||
if (isHighResolution) {
|
||||
clientWord = "Client";
|
||||
} else {
|
||||
clientWord = "App";
|
||||
}
|
||||
snprintf(api_state, sizeof(api_state), "No %ss Connected", clientWord);
|
||||
|
||||
if (service->api_state == service->STATE_BLE) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (BLE)", clientWord);
|
||||
} else if (service->api_state == service->STATE_WIFI) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (WiFi)", clientWord);
|
||||
} else if (service->api_state == service->STATE_SERIAL) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (Serial)", clientWord);
|
||||
} else if (service->api_state == service->STATE_PACKET) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (Internal)", clientWord);
|
||||
} else if (service->api_state == service->STATE_HTTP) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (HTTP)", clientWord);
|
||||
} else if (service->api_state == service->STATE_ETH) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (Ethernet)", clientWord);
|
||||
}
|
||||
if (api_state[0] != '\0') {
|
||||
display->drawString((SCREEN_WIDTH - display->getStringWidth(api_state)) / 2, getTextPositions(display)[line++],
|
||||
api_state);
|
||||
}
|
||||
}
|
||||
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// ****************************
|
||||
|
||||
@@ -515,7 +515,7 @@ void menuHandler::homeBaseMenu()
|
||||
}
|
||||
saveUIConfig();
|
||||
#elif defined(PCA_PIN_EINK_EN)
|
||||
if (uiconfig.screen_brightness == 1) {
|
||||
if (uiconfig.screen_brightness > 0) {
|
||||
uiconfig.screen_brightness = 0;
|
||||
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
|
||||
} else {
|
||||
@@ -784,22 +784,30 @@ void menuHandler::nodeNameLengthMenu()
|
||||
screen->runNow();
|
||||
}
|
||||
};
|
||||
bannerOptions.InitialSelected = config.display.use_long_node_name == true ? 1 : 2;
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::resetNodeDBMenu()
|
||||
{
|
||||
static const char *optionsArray[] = {"Back", "Confirm"};
|
||||
static const char *optionsArray[] = {"Back", "Reset All", "Preserve Favorites"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Confirm Reset NodeDB";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 2;
|
||||
bannerOptions.optionsCount = 3;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 1) {
|
||||
if (selected == 1 || selected == 2) {
|
||||
disableBluetooth();
|
||||
screen->setFrames(Screen::FOCUS_DEFAULT);
|
||||
}
|
||||
if (selected == 1) {
|
||||
LOG_INFO("Initiate node-db reset");
|
||||
nodeDB->resetNodes();
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
} else if (selected == 2) {
|
||||
LOG_INFO("Initiate node-db reset but keeping favorites");
|
||||
nodeDB->resetNodes(1);
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
@@ -935,7 +943,9 @@ void menuHandler::BluetoothToggleMenu()
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 3;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 1 || selected == 2) {
|
||||
if (selected == 0)
|
||||
return;
|
||||
else if (selected != (config.bluetooth.enabled ? 1 : 2)) {
|
||||
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
}
|
||||
|
||||
@@ -213,6 +213,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
#else
|
||||
display->drawString(center_text, getTextPositions(display)[2], messageString);
|
||||
#endif
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -423,6 +424,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
// Draw header at the end to sort out overlapping elements
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
#endif
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)
|
||||
|
||||
@@ -53,7 +53,7 @@ static int scrollIndex = 0;
|
||||
// Utility Functions
|
||||
// =============================
|
||||
|
||||
const char *getSafeNodeName(meshtastic_NodeInfoLite *node)
|
||||
const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node)
|
||||
{
|
||||
const char *name = NULL;
|
||||
static char nodeName[16] = "?";
|
||||
@@ -81,6 +81,28 @@ const char *getSafeNodeName(meshtastic_NodeInfoLite *node)
|
||||
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
|
||||
}
|
||||
|
||||
if (config.display.use_long_node_name == true) {
|
||||
int availWidth = (SCREEN_WIDTH / 2) - 65;
|
||||
if (availWidth < 0)
|
||||
availWidth = 0;
|
||||
|
||||
size_t origLen = strlen(nodeName);
|
||||
while (nodeName[0] && display->getStringWidth(nodeName) > availWidth) {
|
||||
nodeName[strlen(nodeName) - 1] = '\0';
|
||||
}
|
||||
|
||||
// If we actually truncated, append "..." (ensure space remains in buffer)
|
||||
if (strlen(nodeName) < origLen) {
|
||||
size_t len = strlen(nodeName);
|
||||
size_t maxLen = sizeof(nodeName) - 4; // 3 for "..." and 1 for '\0'
|
||||
if (len > maxLen) {
|
||||
nodeName[maxLen] = '\0';
|
||||
len = maxLen;
|
||||
}
|
||||
strcat(nodeName, "...");
|
||||
}
|
||||
}
|
||||
|
||||
return nodeName;
|
||||
}
|
||||
|
||||
@@ -147,7 +169,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||
int timeOffset = (isHighResolution) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
|
||||
|
||||
const char *nodeName = getSafeNodeName(node);
|
||||
const char *nodeName = getSafeNodeName(display, node);
|
||||
|
||||
char timeStr[10];
|
||||
uint32_t seconds = sinceLastSeen(node);
|
||||
@@ -192,7 +214,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
|
||||
int barsXOffset = columnWidth - barsOffset;
|
||||
|
||||
const char *nodeName = getSafeNodeName(node);
|
||||
const char *nodeName = getSafeNodeName(display, node);
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
@@ -236,7 +258,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||
int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
||||
|
||||
const char *nodeName = getSafeNodeName(node);
|
||||
const char *nodeName = getSafeNodeName(display, node);
|
||||
char distStr[10] = "";
|
||||
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
@@ -331,7 +353,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
// Adjust max text width depending on column and screen width
|
||||
int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
||||
|
||||
const char *nodeName = getSafeNodeName(node);
|
||||
const char *nodeName = getSafeNodeName(display, node);
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
@@ -362,11 +384,11 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
float bearing = GeoCoord::bearing(userLat, userLon, nodeLat, nodeLon);
|
||||
float bearingToNode = RAD_TO_DEG * bearing;
|
||||
float relativeBearing = fmod((bearingToNode - myHeading + 360), 360);
|
||||
float angle = relativeBearing * DEG_TO_RAD;
|
||||
// Shrink size by 2px
|
||||
int size = FONT_HEIGHT_SMALL - 5;
|
||||
CompassRenderer::drawArrowToNode(display, centerX, centerY, size, relativeBearing);
|
||||
/*
|
||||
float angle = relativeBearing * DEG_TO_RAD;
|
||||
float halfSize = size / 2.0;
|
||||
|
||||
// Point of the arrow
|
||||
@@ -470,6 +492,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
#endif
|
||||
const int scrollStartY = y + 3;
|
||||
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// =============================
|
||||
|
||||
@@ -552,6 +552,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
||||
// else show nothing
|
||||
}
|
||||
#endif
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// ****************************
|
||||
@@ -563,6 +564,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
int line = 1;
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
// === Header ===
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
@@ -740,7 +742,6 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
int yOffset = (isHighResolution) ? 0 : 5;
|
||||
std::string longNameStr;
|
||||
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
if (ourNode && ourNode->has_user && strlen(ourNode->user.long_name) > 0) {
|
||||
longNameStr = sanitizeString(ourNode->user.long_name);
|
||||
}
|
||||
@@ -771,6 +772,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
||||
}
|
||||
#endif
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// Start Functions to write date/time to the screen
|
||||
@@ -1000,24 +1002,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
||||
const char *displayLine = ""; // Initialize to empty string by default
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
bool usePhoneGPS = (ourNode && nodeDB->hasValidPosition(ourNode) &&
|
||||
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED);
|
||||
|
||||
if (usePhoneGPS) {
|
||||
// Phone-provided GPS is active
|
||||
displayLine = "Phone GPS";
|
||||
int yOffset = (isHighResolution) ? 3 : 1;
|
||||
if (isHighResolution) {
|
||||
NodeListRenderer::drawScaledXBitmap16x16(x, getTextPositions(display)[line] + yOffset - 5, imgSatellite_width,
|
||||
imgSatellite_height, imgSatellite, display);
|
||||
} else {
|
||||
display->drawXbm(x + 1, getTextPositions(display)[line] + yOffset, imgSatellite_width, imgSatellite_height,
|
||||
imgSatellite);
|
||||
}
|
||||
int xOffset = (isHighResolution) ? 6 : 0;
|
||||
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
|
||||
} else if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||
// GPS disabled / not present
|
||||
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||
if (config.position.fixed_position) {
|
||||
displayLine = "Fixed GPS";
|
||||
} else {
|
||||
@@ -1108,9 +1093,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
||||
|
||||
// === Final Row: Altitude ===
|
||||
char altitudeLine[32] = {0};
|
||||
int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
|
||||
? ourNode->position.altitude
|
||||
: geoCoord.getAltitude();
|
||||
int32_t alt = geoCoord.getAltitude();
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0fft", alt * METERS_TO_FEET);
|
||||
} else {
|
||||
@@ -1202,6 +1185,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
||||
}
|
||||
#endif
|
||||
#endif // HAS_GPS
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
@@ -1286,7 +1270,13 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
if (totalIcons == 0)
|
||||
return;
|
||||
|
||||
const size_t iconsPerPage = (SCREEN_WIDTH + spacing) / (iconSize + spacing);
|
||||
const int navPadding = isHighResolution ? 24 : 12; // padding per side
|
||||
|
||||
int usableWidth = SCREEN_WIDTH - (navPadding * 2);
|
||||
if (usableWidth < iconSize)
|
||||
usableWidth = iconSize;
|
||||
|
||||
const size_t iconsPerPage = usableWidth / (iconSize + spacing);
|
||||
const size_t currentPage = currentFrame / iconsPerPage;
|
||||
const size_t pageStart = currentPage * iconsPerPage;
|
||||
const size_t pageEnd = min(pageStart + iconsPerPage, totalIcons);
|
||||
@@ -1357,6 +1347,47 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
display->setColor(WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
// Compact arrow drawer
|
||||
auto drawArrow = [&](bool rightSide) {
|
||||
display->setColor(WHITE);
|
||||
|
||||
const int offset = isHighResolution ? 3 : 1;
|
||||
const int halfH = rectHeight / 2;
|
||||
|
||||
const int top = (y - 2) + (rectHeight - halfH) / 2;
|
||||
const int bottom = top + halfH - 1;
|
||||
const int midY = top + (halfH / 2);
|
||||
|
||||
const int maxW = 4;
|
||||
|
||||
// Determine left X coordinate
|
||||
int baseX = rightSide ? (rectX + rectWidth + offset) : // right arrow
|
||||
(rectX - offset - 1); // left arrow
|
||||
|
||||
for (int yy = top; yy <= bottom; yy++) {
|
||||
int dist = abs(yy - midY);
|
||||
int lineW = maxW - (dist * maxW / (halfH / 2));
|
||||
if (lineW < 1)
|
||||
lineW = 1;
|
||||
|
||||
if (rightSide) {
|
||||
display->drawHorizontalLine(baseX, yy, lineW);
|
||||
} else {
|
||||
display->drawHorizontalLine(baseX - lineW + 1, yy, lineW);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Right arrow
|
||||
if (pageEnd < totalIcons) {
|
||||
drawArrow(true);
|
||||
}
|
||||
|
||||
// Left arrow
|
||||
if (pageStart > 0) {
|
||||
drawArrow(false);
|
||||
}
|
||||
|
||||
// Knock the corners off the square
|
||||
display->setColor(BLACK);
|
||||
display->drawRect(rectX, y - 2, 1, 1);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "emotes.h"
|
||||
|
||||
namespace graphics
|
||||
@@ -275,3 +277,4 @@ const unsigned char bell_icon[] PROGMEM = {
|
||||
#endif
|
||||
|
||||
} // namespace graphics
|
||||
#endif
|
||||
@@ -360,6 +360,10 @@ const uint8_t chirpy_hirez[] = {
|
||||
#define chirpy_small_image_height 8
|
||||
const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
|
||||
|
||||
#define connection_icon_width 7
|
||||
#define connection_icon_height 5
|
||||
const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36};
|
||||
|
||||
#ifdef M5STACK_UNITC6L
|
||||
#include "img/icon_small.xbm"
|
||||
#else
|
||||
|
||||
@@ -13,45 +13,147 @@ void InkHUD::MapApplet::onRender()
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper: draw rounded rectangle centered at x,y
|
||||
auto fillRoundedRect = [&](int16_t cx, int16_t cy, int16_t w, int16_t h, int16_t r, uint16_t color) {
|
||||
int16_t x = cx - (w / 2);
|
||||
int16_t y = cy - (h / 2);
|
||||
|
||||
// center rects
|
||||
fillRect(x + r, y, w - 2 * r, h, color);
|
||||
fillRect(x, y + r, r, h - 2 * r, color);
|
||||
fillRect(x + w - r, y + r, r, h - 2 * r, color);
|
||||
|
||||
// corners
|
||||
fillCircle(x + r, y + r, r, color);
|
||||
fillCircle(x + w - r - 1, y + r, r, color);
|
||||
fillCircle(x + r, y + h - r - 1, r, color);
|
||||
fillCircle(x + w - r - 1, y + h - r - 1, r, color);
|
||||
};
|
||||
|
||||
// Find center of map
|
||||
// - latitude and longitude
|
||||
// - will be placed at X(0.5), Y(0.5)
|
||||
getMapCenter(&latCenter, &lngCenter);
|
||||
|
||||
// Calculate North+East distance of each node to map center
|
||||
// - which nodes to use controlled by virtual shouldDrawNode method
|
||||
calculateAllMarkers();
|
||||
|
||||
// Set the region shown on the map
|
||||
// - default: fit all nodes, plus padding
|
||||
// - maybe overriden by derived applet
|
||||
// - getMapSize *sets* passed parameters (C-style)
|
||||
getMapSize(&widthMeters, &heightMeters);
|
||||
|
||||
// Set the metersToPx conversion value
|
||||
calculateMapScale();
|
||||
|
||||
// Special marker for own node
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
if (ourNode && nodeDB->hasValidPosition(ourNode))
|
||||
drawLabeledMarker(ourNode);
|
||||
|
||||
// Draw all markers
|
||||
// Draw all markers first
|
||||
for (Marker m : markers) {
|
||||
int16_t x = X(0.5) + (m.eastMeters * metersToPx);
|
||||
int16_t y = Y(0.5) - (m.northMeters * metersToPx);
|
||||
|
||||
// Cross Size
|
||||
constexpr uint16_t csMin = 5;
|
||||
constexpr uint16_t csMax = 12;
|
||||
// Add white halo outline first
|
||||
constexpr int outlinePad = 1;
|
||||
int boxSize = 11;
|
||||
int radius = 2; // rounded corner radius
|
||||
|
||||
// Too many hops away
|
||||
if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) // Too many mops
|
||||
printAt(x, y, "!", CENTER, MIDDLE);
|
||||
else if (!m.hasHopsAway) // Unknown hops
|
||||
drawCross(x, y, csMin);
|
||||
else // The fewer hops, the larger the cross
|
||||
drawCross(x, y, map(m.hopsAway, 0, config.lora.hop_limit, csMax, csMin));
|
||||
// White halo background
|
||||
fillRoundedRect(x, y, boxSize + (outlinePad * 2), boxSize + (outlinePad * 2), radius + 1, WHITE);
|
||||
|
||||
// Draw inner box
|
||||
fillRoundedRect(x, y, boxSize, boxSize, radius, BLACK);
|
||||
|
||||
// Text inside
|
||||
setFont(fontSmall);
|
||||
setTextColor(WHITE);
|
||||
|
||||
// Draw actual marker on top
|
||||
if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) {
|
||||
printAt(x + 1, y + 1, "X", CENTER, MIDDLE);
|
||||
} else if (!m.hasHopsAway) {
|
||||
printAt(x + 1, y + 1, "?", CENTER, MIDDLE);
|
||||
} else {
|
||||
char hopStr[4];
|
||||
snprintf(hopStr, sizeof(hopStr), "%d", m.hopsAway);
|
||||
printAt(x, y + 1, hopStr, CENTER, MIDDLE);
|
||||
}
|
||||
|
||||
// Restore default font and color
|
||||
setFont(fontSmall);
|
||||
setTextColor(BLACK);
|
||||
}
|
||||
|
||||
// Dual map scale bars
|
||||
int16_t horizPx = width() * 0.25f;
|
||||
int16_t vertPx = height() * 0.25f;
|
||||
float horizMeters = horizPx / metersToPx;
|
||||
float vertMeters = vertPx / metersToPx;
|
||||
|
||||
auto formatDistance = [&](float meters, char *out, size_t len) {
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
float feet = meters * 3.28084f;
|
||||
if (feet < 528)
|
||||
snprintf(out, len, "%.0f ft", feet);
|
||||
else {
|
||||
float miles = feet / 5280.0f;
|
||||
snprintf(out, len, miles < 10 ? "%.1f mi" : "%.0f mi", miles);
|
||||
}
|
||||
} else {
|
||||
if (meters >= 1000)
|
||||
snprintf(out, len, "%.1f km", meters / 1000.0f);
|
||||
else
|
||||
snprintf(out, len, "%.0f m", meters);
|
||||
}
|
||||
};
|
||||
|
||||
// Horizontal scale bar
|
||||
int16_t horizBarY = height() - 2;
|
||||
int16_t horizBarX = 1;
|
||||
drawLine(horizBarX, horizBarY, horizBarX + horizPx, horizBarY, BLACK);
|
||||
drawLine(horizBarX, horizBarY - 3, horizBarX, horizBarY + 3, BLACK);
|
||||
drawLine(horizBarX + horizPx, horizBarY - 3, horizBarX + horizPx, horizBarY + 3, BLACK);
|
||||
|
||||
char horizLabel[32];
|
||||
formatDistance(horizMeters, horizLabel, sizeof(horizLabel));
|
||||
int16_t horizLabelW = getTextWidth(horizLabel);
|
||||
int16_t horizLabelH = getFont().lineHeight();
|
||||
int16_t horizLabelX = horizBarX + horizPx + 4;
|
||||
int16_t horizLabelY = horizBarY - horizLabelH + 1;
|
||||
fillRect(horizLabelX - 2, horizLabelY - 1, horizLabelW + 4, horizLabelH + 2, WHITE);
|
||||
printAt(horizLabelX, horizBarY, horizLabel, LEFT, BOTTOM);
|
||||
|
||||
// Vertical scale bar
|
||||
int16_t vertBarX = 1;
|
||||
int16_t vertBarBottom = horizBarY;
|
||||
int16_t vertBarTop = vertBarBottom - vertPx;
|
||||
drawLine(vertBarX, vertBarBottom, vertBarX, vertBarTop, BLACK);
|
||||
drawLine(vertBarX - 3, vertBarBottom, vertBarX + 3, vertBarBottom, BLACK);
|
||||
drawLine(vertBarX - 3, vertBarTop, vertBarX + 3, vertBarTop, BLACK);
|
||||
|
||||
char vertTopLabel[32];
|
||||
formatDistance(vertMeters, vertTopLabel, sizeof(vertTopLabel));
|
||||
int16_t topLabelY = vertBarTop - getFont().lineHeight() - 2;
|
||||
int16_t topLabelW = getTextWidth(vertTopLabel);
|
||||
int16_t topLabelH = getFont().lineHeight();
|
||||
fillRect(vertBarX - 2, topLabelY - 1, topLabelW + 6, topLabelH + 2, WHITE);
|
||||
printAt(vertBarX + (topLabelW / 2) + 1, topLabelY + (topLabelH / 2), vertTopLabel, CENTER, MIDDLE);
|
||||
|
||||
char vertBottomLabel[32];
|
||||
formatDistance(vertMeters, vertBottomLabel, sizeof(vertBottomLabel));
|
||||
int16_t bottomLabelY = vertBarBottom + 4;
|
||||
int16_t bottomLabelW = getTextWidth(vertBottomLabel);
|
||||
int16_t bottomLabelH = getFont().lineHeight();
|
||||
fillRect(vertBarX - 2, bottomLabelY - 1, bottomLabelW + 6, bottomLabelH + 2, WHITE);
|
||||
printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
|
||||
|
||||
// Draw our node LAST with full white fill + outline
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
|
||||
Marker self = calculateMarker(ourNode->position.latitude_i * 1e-7, ourNode->position.longitude_i * 1e-7, false, 0);
|
||||
|
||||
int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
|
||||
int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
|
||||
|
||||
// White fill background + halo
|
||||
fillCircle(centerX, centerY, 8, WHITE); // big white base
|
||||
drawCircle(centerX, centerY, 8, WHITE); // crisp edge
|
||||
|
||||
// Black bullseye on top
|
||||
drawCircle(centerX, centerY, 6, BLACK);
|
||||
fillCircle(centerX, centerY, 2, BLACK);
|
||||
|
||||
// Crosshairs
|
||||
drawLine(centerX - 8, centerY, centerX + 8, centerY, BLACK);
|
||||
drawLine(centerX, centerY - 8, centerX, centerY + 8, BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,110 +165,123 @@ void InkHUD::MapApplet::onRender()
|
||||
|
||||
void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||
{
|
||||
// Find mean lat long coords
|
||||
// ============================
|
||||
// - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet
|
||||
// - averages the x, y and z coords
|
||||
// - uses tan to find angles for lat / long degrees
|
||||
// - longitude: triangle formed by x and y (on plane of the equator)
|
||||
// - latitude: triangle formed by z (north south),
|
||||
// and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface
|
||||
// If we have a valid position for our own node, use that as the anchor
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
|
||||
*lat = ourNode->position.latitude_i * 1e-7;
|
||||
*lng = ourNode->position.longitude_i * 1e-7;
|
||||
} else {
|
||||
// Find mean lat long coords
|
||||
// ============================
|
||||
// - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet
|
||||
// - averages the x, y and z coords
|
||||
// - uses tan to find angles for lat / long degrees
|
||||
// - longitude: triangle formed by x and y (on plane of the equator)
|
||||
// - latitude: triangle formed by z (north south),
|
||||
// and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's
|
||||
// surface
|
||||
|
||||
// Working totals, averaged after nodeDB processed
|
||||
uint32_t positionCount = 0;
|
||||
float xAvg = 0;
|
||||
float yAvg = 0;
|
||||
float zAvg = 0;
|
||||
// Working totals, averaged after nodeDB processed
|
||||
uint32_t positionCount = 0;
|
||||
float xAvg = 0;
|
||||
float yAvg = 0;
|
||||
float zAvg = 0;
|
||||
|
||||
// For each node in db
|
||||
for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
// For each node in db
|
||||
for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Skip if no position
|
||||
if (!nodeDB->hasValidPosition(node))
|
||||
continue;
|
||||
// Skip if no position
|
||||
if (!nodeDB->hasValidPosition(node))
|
||||
continue;
|
||||
|
||||
// Skip if derived applet doesn't want to show this node on the map
|
||||
if (!shouldDrawNode(node))
|
||||
continue;
|
||||
// Skip if derived applet doesn't want to show this node on the map
|
||||
if (!shouldDrawNode(node))
|
||||
continue;
|
||||
|
||||
// Latitude and Longitude of node, in radians
|
||||
float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
|
||||
float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
|
||||
// Latitude and Longitude of node, in radians
|
||||
float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
|
||||
float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
|
||||
|
||||
// Convert to cartesian points, with center of earth at 0, 0, 0
|
||||
// Exact distance from center is irrelevant, as we're only interested in the vector
|
||||
float x = cos(latRad) * cos(lngRad);
|
||||
float y = cos(latRad) * sin(lngRad);
|
||||
float z = sin(latRad);
|
||||
// Convert to cartesian points, with center of earth at 0, 0, 0
|
||||
// Exact distance from center is irrelevant, as we're only interested in the vector
|
||||
float x = cos(latRad) * cos(lngRad);
|
||||
float y = cos(latRad) * sin(lngRad);
|
||||
float z = sin(latRad);
|
||||
|
||||
// To find mean values shortly
|
||||
xAvg += x;
|
||||
yAvg += y;
|
||||
zAvg += z;
|
||||
positionCount++;
|
||||
// To find mean values shortly
|
||||
xAvg += x;
|
||||
yAvg += y;
|
||||
zAvg += z;
|
||||
positionCount++;
|
||||
}
|
||||
|
||||
// All NodeDB processed, find mean values
|
||||
xAvg /= positionCount;
|
||||
yAvg /= positionCount;
|
||||
zAvg /= positionCount;
|
||||
|
||||
// Longitude from cartesian coords
|
||||
// (Angle from 3D coords describing a point of globe's surface)
|
||||
/*
|
||||
UK
|
||||
/-------\
|
||||
(Top View) /- -\
|
||||
/- (You) -\
|
||||
/- . -\
|
||||
/- . X -\
|
||||
Asia - ... - USA
|
||||
\- Y -/
|
||||
\- -/
|
||||
\- -/
|
||||
\- -/
|
||||
\- -----/
|
||||
Pacific
|
||||
|
||||
*/
|
||||
|
||||
*lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
|
||||
|
||||
// Latitude from cartesian coords
|
||||
// (Angle from 3D coords describing a point on the globe's surface)
|
||||
// As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
|
||||
// Means we need to first find the hypotenuse which becomes base of our triangle in the second step
|
||||
/*
|
||||
UK North
|
||||
/-------\ (Front View) /-------\
|
||||
(Top View) /- -\ /- -\
|
||||
/- (You) -\ /-(You) -\
|
||||
/- /. -\ /- . -\
|
||||
/- √X²+Y²/ . X -\ /- Z . -\
|
||||
Asia - /... - USA - ..... -
|
||||
\- Y -/ \- √X²+Y² -/
|
||||
\- -/ \- -/
|
||||
\- -/ \- -/
|
||||
\- -/ \- -/
|
||||
\- -----/ \- -----/
|
||||
Pacific South
|
||||
*/
|
||||
|
||||
float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect
|
||||
*lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG;
|
||||
}
|
||||
|
||||
// All NodeDB processed, find mean values
|
||||
xAvg /= positionCount;
|
||||
yAvg /= positionCount;
|
||||
zAvg /= positionCount;
|
||||
|
||||
// Longitude from cartesian coords
|
||||
// (Angle from 3D coords describing a point of globe's surface)
|
||||
/*
|
||||
UK
|
||||
/-------\
|
||||
(Top View) /- -\
|
||||
/- (You) -\
|
||||
/- . -\
|
||||
/- . X -\
|
||||
Asia - ... - USA
|
||||
\- Y -/
|
||||
\- -/
|
||||
\- -/
|
||||
\- -/
|
||||
\- -----/
|
||||
Pacific
|
||||
|
||||
*/
|
||||
|
||||
*lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
|
||||
|
||||
// Latitude from cartesian coords
|
||||
// (Angle from 3D coords describing a point on the globe's surface)
|
||||
// As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
|
||||
// Means we need to first find the hypotenuse which becomes base of our triangle in the second step
|
||||
/*
|
||||
UK North
|
||||
/-------\ (Front View) /-------\
|
||||
(Top View) /- -\ /- -\
|
||||
/- (You) -\ /-(You) -\
|
||||
/- /. -\ /- . -\
|
||||
/- √X²+Y²/ . X -\ /- Z . -\
|
||||
Asia - /... - USA - ..... -
|
||||
\- Y -/ \- √X²+Y² -/
|
||||
\- -/ \- -/
|
||||
\- -/ \- -/
|
||||
\- -/ \- -/
|
||||
\- -----/ \- -----/
|
||||
Pacific South
|
||||
*/
|
||||
|
||||
float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect
|
||||
*lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG;
|
||||
// Use either our node position, or the mean fallback as the center
|
||||
latCenter = *lat;
|
||||
lngCenter = *lng;
|
||||
|
||||
// ----------------------------------------------
|
||||
// This has given us the "mean position"
|
||||
// This will be a position *somewhere* near the center of our nodes.
|
||||
// What we actually want is to place our center so that our outermost nodes end up on the border of our map.
|
||||
// The only real use of our "mean position" is to give us a reference frame:
|
||||
// which direction is east, and which is west.
|
||||
// This has given us either:
|
||||
// - our actual position (preferred), or
|
||||
// - a mean position (fallback if we had no fix)
|
||||
//
|
||||
// What we actually want is to place our center so that our outermost nodes
|
||||
// end up on the border of our map. The only real use of our "center" is to give
|
||||
// us a reference frame: which direction is east, and which is west.
|
||||
//------------------------------------------------
|
||||
|
||||
// Find furthest nodes from "mean lat long"
|
||||
// Find furthest nodes from our center
|
||||
// ========================================
|
||||
|
||||
float northernmost = latCenter;
|
||||
float southernmost = latCenter;
|
||||
float easternmost = lngCenter;
|
||||
@@ -184,14 +299,14 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||
continue;
|
||||
|
||||
// Check for a new top or bottom latitude
|
||||
float lat = node->position.latitude_i * 1e-7;
|
||||
northernmost = max(northernmost, lat);
|
||||
southernmost = min(southernmost, lat);
|
||||
float latNode = node->position.latitude_i * 1e-7;
|
||||
northernmost = max(northernmost, latNode);
|
||||
southernmost = min(southernmost, latNode);
|
||||
|
||||
// Longitude is trickier
|
||||
float lng = node->position.longitude_i * 1e-7;
|
||||
float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
|
||||
float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
|
||||
float lngNode = node->position.longitude_i * 1e-7;
|
||||
float degEastward = fmod(((lngNode - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
|
||||
float degWestward = abs(fmod(((lngNode - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
|
||||
if (degEastward < degWestward)
|
||||
easternmost = max(easternmost, lngCenter + degEastward);
|
||||
else
|
||||
@@ -250,7 +365,6 @@ InkHUD::MapApplet::Marker InkHUD::MapApplet::calculateMarker(float lat, float ln
|
||||
m.hopsAway = hopsAway;
|
||||
return m;
|
||||
}
|
||||
|
||||
// Draw a marker on the map for a node, with a shortname label, and backing box
|
||||
void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||
{
|
||||
@@ -324,6 +438,18 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||
textX = labelX + paddingW;
|
||||
}
|
||||
|
||||
// Prevent overlap with scale bars and their labels
|
||||
// Define a "safe zone" in the bottom-left where the scale bars and text are drawn
|
||||
constexpr int16_t safeZoneHeight = 28; // adjust based on your label font height
|
||||
constexpr int16_t safeZoneWidth = 60; // adjust based on horizontal label width zone
|
||||
bool overlapsScale = (labelY + labelH > height() - safeZoneHeight) && (labelX < safeZoneWidth);
|
||||
|
||||
// If it overlaps, shift label upward slightly above the safe zone
|
||||
if (overlapsScale) {
|
||||
labelY = height() - safeZoneHeight - labelH - 2;
|
||||
textY = labelY + (labelH / 2);
|
||||
}
|
||||
|
||||
// Backing box
|
||||
fillRect(labelX, labelY, labelW, labelH, WHITE);
|
||||
drawRect(labelX, labelY, labelW, labelH, BLACK);
|
||||
|
||||
@@ -127,6 +127,11 @@ void InkHUD::NodeListApplet::onRender()
|
||||
// Y value (top) of the current card. Increases as we draw.
|
||||
uint16_t cardTopY = headerDivY + padDivH;
|
||||
|
||||
// Clean up deleted nodes before drawing
|
||||
cards.erase(
|
||||
std::remove_if(cards.begin(), cards.end(), [](const CardInfo &c) { return nodeDB->getMeshNode(c.nodeNum) == nullptr; }),
|
||||
cards.end());
|
||||
|
||||
// -- Each node in list --
|
||||
for (auto card = cards.begin(); card != cards.end(); ++card) {
|
||||
|
||||
@@ -141,6 +146,11 @@ void InkHUD::NodeListApplet::onRender()
|
||||
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
|
||||
|
||||
// Skip deleted nodes
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// -- Shortname --
|
||||
// Parse special chars in the short name
|
||||
// Use "?" if unknown
|
||||
@@ -188,7 +198,7 @@ void InkHUD::NodeListApplet::onRender()
|
||||
drawSignalIndicator(signalX, signalY, signalW, signalH, signal);
|
||||
}
|
||||
// Otherwise, print "hops away" info, if available
|
||||
else if (hopsAway != CardInfo::HOPS_UNKNOWN) {
|
||||
else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) {
|
||||
std::string hopString = to_string(node->hops_away);
|
||||
hopString += " Hop";
|
||||
if (node->hops_away != 1)
|
||||
|
||||
@@ -709,7 +709,7 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t
|
||||
// Voltage
|
||||
float voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
|
||||
char voltageStr[6]; // "XX.XV"
|
||||
sprintf(voltageStr, "%.1fV", voltage);
|
||||
sprintf(voltageStr, "%.2fV", voltage);
|
||||
printAt(colC[0], labelT, "Bat", CENTER, TOP);
|
||||
printAt(colC[0], valT, voltageStr, CENTER, TOP);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "InputBroker.h"
|
||||
#include "PowerFSM.h" // needed for event trigger
|
||||
#include "configuration.h"
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
|
||||
InputBroker *inputBroker = nullptr;
|
||||
|
||||
InputBroker::InputBroker()
|
||||
{
|
||||
#ifdef HAS_FREE_RTOS
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
inputEventQueue = xQueueCreate(5, sizeof(InputEvent));
|
||||
pollSoonQueue = xQueueCreate(5, sizeof(InputPollable *));
|
||||
xTaskCreate(pollSoonWorker, "input-pollSoon", 2 * 1024, this, 10, &pollSoonTask);
|
||||
@@ -17,7 +19,7 @@ void InputBroker::registerSource(Observable<const InputEvent *> *source)
|
||||
this->inputEventObserver.observe(source);
|
||||
}
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
void InputBroker::requestPollSoon(InputPollable *pollable)
|
||||
{
|
||||
if (xPortInIsrContext() == pdTRUE) {
|
||||
@@ -48,11 +50,17 @@ void InputBroker::processInputEventQueue()
|
||||
int InputBroker::handleInputEvent(const InputEvent *event)
|
||||
{
|
||||
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
|
||||
|
||||
if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule &&
|
||||
moduleConfig.external_notification.enabled && externalNotificationModule->nagging()) {
|
||||
externalNotificationModule->stopNow();
|
||||
}
|
||||
|
||||
this->notifyObservers(event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
void InputBroker::pollSoonWorker(void *p)
|
||||
{
|
||||
InputBroker *instance = (InputBroker *)p;
|
||||
|
||||
@@ -59,7 +59,7 @@ class InputBroker : public Observable<const InputEvent *>
|
||||
InputBroker();
|
||||
void registerSource(Observable<const InputEvent *> *source);
|
||||
void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
|
||||
#ifdef HAS_FREE_RTOS
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
void requestPollSoon(InputPollable *pollable);
|
||||
void queueInputEvent(const InputEvent *event);
|
||||
void processInputEventQueue();
|
||||
@@ -69,7 +69,7 @@ class InputBroker : public Observable<const InputEvent *>
|
||||
int handleInputEvent(const InputEvent *event);
|
||||
|
||||
private:
|
||||
#ifdef HAS_FREE_RTOS
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
QueueHandle_t inputEventQueue;
|
||||
QueueHandle_t pollSoonQueue;
|
||||
TaskHandle_t pollSoonTask;
|
||||
|
||||
@@ -57,7 +57,7 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = {
|
||||
{0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},
|
||||
{0x20, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, '0'},
|
||||
{0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift
|
||||
};
|
||||
|
||||
|
||||
15
src/main.cpp
15
src/main.cpp
@@ -436,6 +436,12 @@ void setup()
|
||||
|
||||
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
|
||||
|
||||
#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));
|
||||
DEBUG_PORT.printf("Debug mute is enabled, there will be no serial output.\r\n");
|
||||
#endif
|
||||
|
||||
initDeepSleep();
|
||||
|
||||
#if defined(MODEM_POWER_EN)
|
||||
@@ -841,7 +847,14 @@ void setup()
|
||||
SPI.begin();
|
||||
}
|
||||
#elif !defined(ARCH_ESP32) // ARCH_RP2040
|
||||
#if defined(RAK3401) || defined(RAK13302)
|
||||
pinMode(WB_IO2, OUTPUT);
|
||||
digitalWrite(WB_IO2, HIGH);
|
||||
SPI1.setPins(LORA_MISO, LORA_SCK, LORA_MOSI);
|
||||
SPI1.begin();
|
||||
#else
|
||||
SPI.begin();
|
||||
#endif
|
||||
#else
|
||||
// ESP32
|
||||
#if defined(HW_SPI1_DEVICE)
|
||||
@@ -1582,7 +1595,7 @@ void loop()
|
||||
#endif
|
||||
|
||||
service->loop();
|
||||
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS)
|
||||
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
if (inputBroker)
|
||||
inputBroker->processInputEventQueue();
|
||||
#endif
|
||||
|
||||
@@ -92,10 +92,10 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
|
||||
LOG_DEBUG("Node %d or their public_key not found", toNode);
|
||||
return false;
|
||||
}
|
||||
if (!crypto->setDHPublicKey(remotePublic.bytes)) {
|
||||
if (!setDHPublicKey(remotePublic.bytes)) {
|
||||
return false;
|
||||
}
|
||||
crypto->hash(shared_key, 32);
|
||||
hash(shared_key, 32);
|
||||
initNonce(fromNode, packetNum, extraNonceTmp);
|
||||
|
||||
// Calculate the shared secret with the destination node and encrypt
|
||||
@@ -134,10 +134,10 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_publ
|
||||
}
|
||||
|
||||
// Calculate the shared secret with the sending node and decrypt
|
||||
if (!crypto->setDHPublicKey(remotePublic.bytes)) {
|
||||
if (!setDHPublicKey(remotePublic.bytes)) {
|
||||
return false;
|
||||
}
|
||||
crypto->hash(shared_key, 32);
|
||||
hash(shared_key, 32);
|
||||
|
||||
initNonce(fromNode, packetNum, extraNonce);
|
||||
printBytes("Attempt decrypt with nonce: ", nonce, 13);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#ifdef USERPREFS_RINGTONE_NAG_SECS
|
||||
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
|
||||
#else
|
||||
#define default_ringtone_nag_secs 60
|
||||
#define default_ringtone_nag_secs 15
|
||||
#endif
|
||||
|
||||
#define default_mqtt_address "mqtt.meshtastic.org"
|
||||
@@ -84,4 +84,4 @@ class Default
|
||||
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default)
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -31,33 +31,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected
|
||||
|
||||
// Handle hop_limit upgrade scenario for rebroadcasters
|
||||
// isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
|
||||
if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
|
||||
// wasSeenRecently() reports false in upgrade cases so we handle replacement before the duplicate short-circuit
|
||||
// If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
|
||||
// rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
|
||||
uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
|
||||
if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
|
||||
LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
|
||||
p->hop_limit, dropThreshold);
|
||||
|
||||
if (nodeDB)
|
||||
nodeDB->updateFrom(*p);
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
|
||||
traceRouteModule->processUpgradedPacket(*p);
|
||||
#endif
|
||||
|
||||
perhapsRebroadcast(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 (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
|
||||
return true; // we handled it, so stop processing
|
||||
}
|
||||
|
||||
if (seenRecently) {
|
||||
@@ -70,8 +45,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
if (isRepeated) {
|
||||
LOG_DEBUG("Repeated reliable tx");
|
||||
// Check if it's still in the Tx queue, if not, we have to relay it again
|
||||
if (!findInTxQueue(p->from, p->id))
|
||||
if (!findInTxQueue(p->from, p->id)) {
|
||||
reprocessPacket(p);
|
||||
perhapsRebroadcast(p);
|
||||
}
|
||||
} else {
|
||||
perhapsCancelDupe(p);
|
||||
}
|
||||
@@ -82,6 +59,40 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
return Router::shouldFilterReceived(p);
|
||||
}
|
||||
|
||||
bool FloodingRouter::perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
// isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
|
||||
if (isRebroadcaster() && iface && p->hop_limit > 0) {
|
||||
// If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
|
||||
// rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
|
||||
uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
|
||||
if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
|
||||
LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
|
||||
p->hop_limit, dropThreshold);
|
||||
|
||||
reprocessPacket(p);
|
||||
perhapsRebroadcast(p);
|
||||
|
||||
rxDupe++;
|
||||
// We already enqueued the improved copy, so make sure the incoming packet stops here.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (nodeDB)
|
||||
nodeDB->updateFrom(*p);
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
|
||||
traceRouteModule->processUpgradedPacket(*p);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
|
||||
@@ -121,41 +132,6 @@ bool FloodingRouter::isRebroadcaster()
|
||||
config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
|
||||
}
|
||||
|
||||
void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
|
||||
if (p->id != 0) {
|
||||
if (isRebroadcaster()) {
|
||||
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
|
||||
|
||||
// Use shared logic to determine if hop_limit should be decremented
|
||||
if (shouldDecrementHopLimit(p)) {
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
} else {
|
||||
LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit");
|
||||
}
|
||||
#if USERPREFS_EVENT_MODE
|
||||
if (tosend->hop_limit > 2) {
|
||||
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
|
||||
tosend->hop_start -= (tosend->hop_limit - 2);
|
||||
tosend->hop_limit = 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
|
||||
|
||||
LOG_INFO("Rebroadcast received floodmsg");
|
||||
// Note: we are careful to resend using the original senders node id
|
||||
send(tosend);
|
||||
} else {
|
||||
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG("Ignore 0 id broadcast");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
||||
{
|
||||
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
|
||||
|
||||
@@ -27,10 +27,6 @@
|
||||
*/
|
||||
class FloodingRouter : public Router
|
||||
{
|
||||
private:
|
||||
/* Check if we should rebroadcast this packet, and do so if needed */
|
||||
void perhapsRebroadcast(const meshtastic_MeshPacket *p);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
@@ -59,6 +55,17 @@ class FloodingRouter : public Router
|
||||
*/
|
||||
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
|
||||
|
||||
/* Check if we should rebroadcast this packet, and do so if needed */
|
||||
virtual bool perhapsRebroadcast(const meshtastic_MeshPacket *p) = 0;
|
||||
|
||||
/* Check if we should handle an upgraded packet (with higher hop_limit)
|
||||
* @return true if we handled it (so stop processing)
|
||||
*/
|
||||
bool perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p);
|
||||
|
||||
/* Call when we receive a packet that needs some reprocessing, but afterwards should be filtered */
|
||||
void reprocessPacket(const meshtastic_MeshPacket *p);
|
||||
|
||||
// Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of
|
||||
// the same packet
|
||||
bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
|
||||
|
||||
@@ -79,6 +79,18 @@ class MeshService
|
||||
uint32_t oldFromNum = 0;
|
||||
|
||||
public:
|
||||
enum APIState {
|
||||
STATE_DISCONNECTED, // Initial state, no API is connected
|
||||
STATE_BLE,
|
||||
STATE_WIFI,
|
||||
STATE_SERIAL,
|
||||
STATE_PACKET,
|
||||
STATE_HTTP,
|
||||
STATE_ETH
|
||||
};
|
||||
|
||||
APIState api_state = STATE_DISCONNECTED;
|
||||
|
||||
static bool isTextPayload(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (moduleConfig.range_test.enabled && p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) {
|
||||
|
||||
@@ -43,31 +43,8 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
&wasUpgraded); // Updates history; returns false when an upgrade is detected
|
||||
|
||||
// Handle hop_limit upgrade scenario for rebroadcasters
|
||||
// isRebroadcaster() is duplicated in perhapsRelay(), but this avoids confusing log messages
|
||||
if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
|
||||
// Upgrade detection bypasses the duplicate short-circuit so we replace the queued packet before exiting
|
||||
uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
|
||||
if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
|
||||
LOG_DEBUG("Processing upgraded packet 0x%08x for relay with hop limit %d (dropping queued < %d)", p->id, p->hop_limit,
|
||||
dropThreshold);
|
||||
|
||||
if (nodeDB)
|
||||
nodeDB->updateFrom(*p);
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
|
||||
traceRouteModule->processUpgradedPacket(*p);
|
||||
#endif
|
||||
|
||||
perhapsRelay(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 (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
|
||||
return true; // we handled it, so stop processing
|
||||
}
|
||||
|
||||
if (seenRecently) {
|
||||
@@ -82,14 +59,20 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
if (wasFallback) {
|
||||
LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
|
||||
// Check if it's still in the Tx queue, if not, we have to relay it again
|
||||
if (!findInTxQueue(p->from, p->id))
|
||||
perhapsRelay(p);
|
||||
if (!findInTxQueue(p->from, p->id)) {
|
||||
reprocessPacket(p);
|
||||
perhapsRebroadcast(p);
|
||||
}
|
||||
} else {
|
||||
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
|
||||
// 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) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
||||
if (!findInTxQueue(p->from, p->id)) {
|
||||
reprocessPacket(p);
|
||||
if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
||||
}
|
||||
}
|
||||
} else if (!weWereNextHop) {
|
||||
perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
|
||||
}
|
||||
@@ -107,13 +90,14 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
|
||||
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
|
||||
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
|
||||
if (isAckorReply) {
|
||||
// Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
|
||||
// not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
|
||||
// Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from"
|
||||
// is not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the
|
||||
// destination
|
||||
if (p->from != 0) {
|
||||
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
|
||||
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
|
||||
// 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);
|
||||
@@ -134,34 +118,49 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
|
||||
}
|
||||
}
|
||||
|
||||
perhapsRelay(p);
|
||||
perhapsRebroadcast(p);
|
||||
|
||||
// handle the packet as normal
|
||||
Router::sniffReceived(p, c);
|
||||
}
|
||||
|
||||
/* Check if we should be relaying this packet if so, do so. */
|
||||
bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
|
||||
/* Check if we should be rebroadcasting this packet if so, do so. */
|
||||
bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
|
||||
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
|
||||
if (p->id != 0) {
|
||||
if (isRebroadcaster()) {
|
||||
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
|
||||
LOG_INFO("Relaying received message coming from %x", p->relay_node);
|
||||
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
|
||||
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
|
||||
LOG_INFO("Rebroadcast received message coming from %x", p->relay_node);
|
||||
|
||||
// Use shared logic to determine if hop_limit should be decremented
|
||||
if (shouldDecrementHopLimit(p)) {
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
} else {
|
||||
LOG_INFO("Router/CLIENT_BASE-to-favorite-router/CLIENT_BASE relay: preserving hop_limit");
|
||||
// Use shared logic to determine if hop_limit should be decremented
|
||||
if (shouldDecrementHopLimit(p)) {
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
} else {
|
||||
LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
|
||||
}
|
||||
#if USERPREFS_EVENT_MODE
|
||||
if (tosend->hop_limit > 2) {
|
||||
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
|
||||
tosend->hop_start -= (tosend->hop_limit - 2);
|
||||
tosend->hop_limit = 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {
|
||||
FloodingRouter::send(tosend);
|
||||
} else {
|
||||
NextHopRouter::send(tosend);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NextHopRouter::send(tosend);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
|
||||
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG("Ignore 0 id broadcast");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,13 +230,13 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
|
||||
}
|
||||
}
|
||||
|
||||
// Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
|
||||
// get scheduled again. (This is the core of stopRetransmission.)
|
||||
// Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it
|
||||
// doesn't get scheduled again. (This is the core of stopRetransmission.)
|
||||
auto numErased = pending.erase(key);
|
||||
assert(numErased == 1);
|
||||
|
||||
// When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
|
||||
// to startRetransmission.
|
||||
// When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the
|
||||
// call to startRetransmission.
|
||||
packetPool.release(p);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -148,7 +148,7 @@ class NextHopRouter : public FloodingRouter
|
||||
*/
|
||||
uint8_t getNextHop(NodeNum to, uint8_t relay_node);
|
||||
|
||||
/** Check if we should be relaying this packet if so, do so.
|
||||
* @return true if we did relay */
|
||||
bool perhapsRelay(const meshtastic_MeshPacket *p);
|
||||
/** Check if we should be rebroadcasting this packet if so, do so.
|
||||
* @return true if we did rebroadcast */
|
||||
bool perhapsRebroadcast(const meshtastic_MeshPacket *p) override;
|
||||
};
|
||||
@@ -653,7 +653,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
|
||||
|
||||
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
|
||||
defined(ELECROW_PANEL)) && \
|
||||
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT)) && \
|
||||
HAS_TFT
|
||||
// switch BT off by default; use TFT programming mode or hotkey to enable
|
||||
config.bluetooth.enabled = false;
|
||||
@@ -978,12 +978,25 @@ void NodeDB::installDefaultChannels()
|
||||
channelFile.version = DEVICESTATE_CUR_VER;
|
||||
}
|
||||
|
||||
void NodeDB::resetNodes()
|
||||
void NodeDB::resetNodes(bool keepFavorites)
|
||||
{
|
||||
if (!config.position.fixed_position)
|
||||
clearLocalPosition();
|
||||
numMeshNodes = 1;
|
||||
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
|
||||
if (keepFavorites) {
|
||||
LOG_INFO("Clearing node database - preserving favorites");
|
||||
for (size_t i = 0; i < meshNodes->size(); i++) {
|
||||
meshtastic_NodeInfoLite &node = meshNodes->at(i);
|
||||
if (i > 0 && !node.is_favorite) {
|
||||
node = meshtastic_NodeInfoLite();
|
||||
} else {
|
||||
numMeshNodes += 1;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
LOG_INFO("Clearing node database - removing favorites");
|
||||
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
|
||||
}
|
||||
devicestate.has_rx_text_message = false;
|
||||
devicestate.has_rx_waypoint = false;
|
||||
saveNodeDatabaseToDisk();
|
||||
@@ -1632,13 +1645,32 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
// If should_ignore is set,
|
||||
// we need to clear the public key and other cruft, in addition to setting the node as ignored
|
||||
info->is_ignored = true;
|
||||
info->is_favorite = false;
|
||||
info->has_device_metrics = false;
|
||||
info->has_position = false;
|
||||
info->user.public_key.size = 0;
|
||||
info->user.public_key.bytes[0] = 0;
|
||||
} else {
|
||||
info->last_heard = getValidTime(RTCQualityNTP);
|
||||
info->is_favorite = true;
|
||||
/* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
|
||||
* public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
|
||||
*/
|
||||
|
||||
/* "Boring old nodes" are the first to be evicted out of the node database when full. This includes a newly-zeroed
|
||||
* nodeinfo because it has: !is_favorite && last_heard==0. To keep this from happening when we addFromContact, we set the
|
||||
* new node as a favorite, and we leave last_heard alone (even if it's zero).
|
||||
*/
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
|
||||
// Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
|
||||
// without the user doing so deliberately. We don't normally expect users to use a CLIENT_BASE to send DMs or to add
|
||||
// contacts, but we should make sure it doesn't auto-favorite in case they do. Instead, as a workaround, we'll set
|
||||
// last_heard to now, so that the add_contact node doesn't immediately get evicted.
|
||||
info->last_heard = getTime();
|
||||
} else {
|
||||
// Normal case: set is_favorite to prevent expiration.
|
||||
// last_heard will remain as-is (or remain 0 if this entry wasn't in the nodeDB).
|
||||
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;
|
||||
|
||||
@@ -229,7 +229,8 @@ class NodeDB
|
||||
*/
|
||||
size_t getNumOnlineMeshNodes(bool localOnly = false);
|
||||
|
||||
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum);
|
||||
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(bool keepFavorites = false),
|
||||
removeNodeByNum(NodeNum nodeNum);
|
||||
|
||||
bool factoryReset(bool eraseBleBonds = false);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ typedef struct PacketCacheEntry {
|
||||
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
|
||||
uint8_t : 8; // Reserved for future use
|
||||
};
|
||||
};
|
||||
} PacketCacheEntry;
|
||||
|
||||
@@ -94,7 +94,6 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
|
||||
LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit,
|
||||
p->hop_limit);
|
||||
*wasUpgraded = true;
|
||||
seenRecently = false; // Allow router processing but prevent duplicate app delivery
|
||||
} else if (wasUpgraded) {
|
||||
*wasUpgraded = false; // Initialize to false if not an upgrade
|
||||
}
|
||||
|
||||
@@ -57,6 +57,9 @@ void PhoneAPI::handleStartConfig()
|
||||
#endif
|
||||
}
|
||||
|
||||
// Allow subclasses to prepare for high-throughput config traffic
|
||||
onConfigStart();
|
||||
|
||||
// even if we were already connected - restart our state machine
|
||||
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
|
||||
// If client only wants node info, jump directly to sending nodes
|
||||
@@ -71,7 +74,7 @@ void PhoneAPI::handleStartConfig()
|
||||
spiLock->unlock();
|
||||
LOG_DEBUG("Got %d files in manifest", filesManifest.size());
|
||||
|
||||
LOG_INFO("Start API client config");
|
||||
LOG_INFO("Start API client config millis=%u", millis());
|
||||
// Protect against concurrent BLE callbacks: they run in NimBLE's FreeRTOS task and also touch nodeInfoQueue.
|
||||
{
|
||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||
@@ -84,6 +87,18 @@ void PhoneAPI::handleStartConfig()
|
||||
void PhoneAPI::close()
|
||||
{
|
||||
LOG_DEBUG("PhoneAPI::close()");
|
||||
if (service->api_state == service->STATE_BLE && api_type == TYPE_BLE)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
else if (service->api_state == service->STATE_WIFI && api_type == TYPE_WIFI)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
else if (service->api_state == service->STATE_SERIAL && api_type == TYPE_SERIAL)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
else if (service->api_state == service->STATE_PACKET && api_type == TYPE_PACKET)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
else if (service->api_state == service->STATE_HTTP && api_type == TYPE_HTTP)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
else if (service->api_state == service->STATE_ETH && api_type == TYPE_ETH)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
|
||||
if (state != STATE_SEND_NOTHING) {
|
||||
state = STATE_SEND_NOTHING;
|
||||
@@ -453,7 +468,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
break;
|
||||
|
||||
case STATE_SEND_OTHER_NODEINFOS: {
|
||||
LOG_DEBUG("Send known nodes");
|
||||
if (readIndex == 2) { // readIndex==2 will be true for the first non-us node
|
||||
LOG_INFO("Start sending nodeinfos millis=%u", millis());
|
||||
}
|
||||
|
||||
meshtastic_NodeInfo infoToSend = {};
|
||||
{
|
||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||
@@ -470,13 +488,22 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
if (infoToSend.num != 0) {
|
||||
// Just in case we stored a different user.id in the past, but should never happen going forward
|
||||
sprintf(infoToSend.user.id, "!%08x", infoToSend.num);
|
||||
LOG_DEBUG("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", infoToSend.num, infoToSend.last_heard,
|
||||
infoToSend.user.id, infoToSend.user.long_name);
|
||||
|
||||
// Logging this really slows down sending nodes on initial connection because the serial console is so slow, so only
|
||||
// uncomment if you really need to:
|
||||
// LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
|
||||
// nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
|
||||
|
||||
// Occasional progress logging. (readIndex==2 will be true for the first non-us node)
|
||||
if (readIndex == 2 || readIndex % 20 == 0) {
|
||||
LOG_DEBUG("nodeinfo: %d/%d", readIndex, nodeDB->getNumMeshNodes());
|
||||
}
|
||||
|
||||
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
|
||||
fromRadioScratch.node_info = infoToSend;
|
||||
prefetchNodeInfos();
|
||||
} else {
|
||||
LOG_DEBUG("Done sending nodeinfo");
|
||||
LOG_DEBUG("Done sending %d of %d nodeinfos millis=%u", readIndex, nodeDB->getNumMeshNodes(), millis());
|
||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||
nodeInfoQueue.clear();
|
||||
state = STATE_SEND_FILEMANIFEST;
|
||||
@@ -558,11 +585,28 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
|
||||
void PhoneAPI::sendConfigComplete()
|
||||
{
|
||||
LOG_INFO("Config Send Complete");
|
||||
LOG_INFO("Config Send Complete millis=%u", millis());
|
||||
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag;
|
||||
fromRadioScratch.config_complete_id = config_nonce;
|
||||
config_nonce = 0;
|
||||
state = STATE_SEND_PACKETS;
|
||||
if (api_type == TYPE_BLE) {
|
||||
service->api_state = service->STATE_BLE;
|
||||
} else if (api_type == TYPE_WIFI) {
|
||||
service->api_state = service->STATE_WIFI;
|
||||
} else if (api_type == TYPE_SERIAL) {
|
||||
service->api_state = service->STATE_SERIAL;
|
||||
} else if (api_type == TYPE_PACKET) {
|
||||
service->api_state = service->STATE_PACKET;
|
||||
} else if (api_type == TYPE_HTTP) {
|
||||
service->api_state = service->STATE_HTTP;
|
||||
} else if (api_type == TYPE_ETH) {
|
||||
service->api_state = service->STATE_ETH;
|
||||
}
|
||||
|
||||
// Allow subclasses to know we've entered steady-state so they can lower power consumption
|
||||
onConfigComplete();
|
||||
|
||||
pauseBluetoothLogging = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ class PhoneAPI
|
||||
bool available();
|
||||
|
||||
bool isConnected() { return state != STATE_SEND_NOTHING; }
|
||||
bool isSendingPackets() { return state == STATE_SEND_PACKETS; }
|
||||
|
||||
protected:
|
||||
/// Our fromradio packet while it is being assembled
|
||||
@@ -158,9 +159,26 @@ class PhoneAPI
|
||||
*/
|
||||
virtual void onNowHasData(uint32_t fromRadioNum) {}
|
||||
|
||||
/// Subclasses can use these lifecycle hooks for transport-specific behavior around config/steady-state
|
||||
/// (i.e. BLE connection params)
|
||||
virtual void onConfigStart() {}
|
||||
virtual void onConfigComplete() {}
|
||||
|
||||
/// begin a new connection
|
||||
void handleStartConfig();
|
||||
|
||||
enum APIType {
|
||||
TYPE_NONE, // Initial state, don't send anything until the client starts asking for config
|
||||
TYPE_BLE,
|
||||
TYPE_WIFI,
|
||||
TYPE_SERIAL,
|
||||
TYPE_PACKET,
|
||||
TYPE_HTTP,
|
||||
TYPE_ETH
|
||||
};
|
||||
|
||||
APIType api_type = TYPE_NONE;
|
||||
|
||||
private:
|
||||
void releasePhonePacket();
|
||||
|
||||
|
||||
@@ -260,6 +260,7 @@ void RF95Interface::addReceiveMetadata(meshtastic_MeshPacket *mp)
|
||||
{
|
||||
mp->rx_snr = lora->getSNR();
|
||||
mp->rx_rssi = lround(lora->getRSSI());
|
||||
LOG_DEBUG("Corrected frequency offset: %f", lora->getFrequencyError());
|
||||
}
|
||||
|
||||
void RF95Interface::setStandby()
|
||||
|
||||
@@ -272,10 +272,10 @@ uint32_t RadioInterface::getTxDelayMsec()
|
||||
uint8_t RadioInterface::getCWsize(float snr)
|
||||
{
|
||||
// The minimum value for a LoRa SNR
|
||||
const uint32_t SNR_MIN = -20;
|
||||
const int32_t SNR_MIN = -20;
|
||||
|
||||
// The maximum value for a LoRa SNR
|
||||
const uint32_t SNR_MAX = 10;
|
||||
const int32_t SNR_MAX = 10;
|
||||
|
||||
return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
|
||||
}
|
||||
|
||||
@@ -289,12 +289,7 @@ void RadioLibInterface::onNotify(uint32_t notification)
|
||||
// actual transmission as short as possible
|
||||
txp = txQueue.dequeue();
|
||||
assert(txp);
|
||||
bool sent = startSend(txp);
|
||||
if (sent) {
|
||||
// Packet has been sent, count it toward our TX airtime utilization.
|
||||
uint32_t xmitMsec = getPacketTime(txp);
|
||||
airTime->logAirtime(TX_LOG, xmitMsec);
|
||||
}
|
||||
startSend(txp);
|
||||
LOG_DEBUG("%d packets remain in the TX queue", txQueue.getMaxLen() - txQueue.getFree());
|
||||
}
|
||||
}
|
||||
@@ -413,6 +408,10 @@ void RadioLibInterface::completeSending()
|
||||
sendingPacket = NULL;
|
||||
|
||||
if (p) {
|
||||
// Packet has been sent, count it toward our TX airtime utilization.
|
||||
uint32_t xmitMsec = getPacketTime(p);
|
||||
airTime->logAirtime(TX_LOG, xmitMsec);
|
||||
|
||||
txGood++;
|
||||
if (!isFromUs(p))
|
||||
txRelay++;
|
||||
|
||||
@@ -35,6 +35,15 @@
|
||||
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
|
||||
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
||||
|
||||
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
|
||||
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
|
||||
#elif defined(ARCH_STM32WL)
|
||||
// On STM32 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 + \
|
||||
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
||||
|
||||
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
|
||||
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
|
||||
#else
|
||||
@@ -470,6 +479,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
||||
LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
|
||||
} else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) {
|
||||
LOG_ERROR("Invalid portnum (bad psk?)!");
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
} else if (!owner.is_licensed && isToUs(p) && decodedtmp.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) {
|
||||
LOG_WARN("Rejecting legacy DM");
|
||||
return DecodeState::DECODE_FAILURE;
|
||||
#endif
|
||||
} else {
|
||||
p->decoded = decodedtmp;
|
||||
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
||||
|
||||
@@ -19,6 +19,7 @@ PacketAPI *PacketAPI::create(PacketServer *_server)
|
||||
PacketAPI::PacketAPI(PacketServer *_server)
|
||||
: concurrency::OSThread("PacketAPI"), isConnected(false), programmingMode(false), server(_server)
|
||||
{
|
||||
api_type = TYPE_PACKET;
|
||||
}
|
||||
|
||||
int32_t PacketAPI::runOnce()
|
||||
|
||||
@@ -25,6 +25,7 @@ void deInitApiServer()
|
||||
|
||||
WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : ServerAPI(_client)
|
||||
{
|
||||
api_type = TYPE_WIFI;
|
||||
LOG_INFO("Incoming wifi connection");
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ void initApiServer(int port)
|
||||
ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client)
|
||||
{
|
||||
LOG_INFO("Incoming ethernet connection");
|
||||
api_type = TYPE_ETH;
|
||||
}
|
||||
|
||||
ethServerPort::ethServerPort(int port) : APIServerPort(port) {}
|
||||
|
||||
@@ -272,8 +272,9 @@ typedef struct _meshtastic_AdminMessage {
|
||||
int32_t shutdown_seconds;
|
||||
/* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */
|
||||
int32_t factory_reset_config;
|
||||
/* Tell the node to reset the nodedb. */
|
||||
int32_t nodedb_reset;
|
||||
/* Tell the node to reset the nodedb.
|
||||
When true, favorites are preserved through reset. */
|
||||
bool nodedb_reset;
|
||||
};
|
||||
/* The node generates this key and sends it with any get_x_response packets.
|
||||
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
|
||||
@@ -459,7 +460,7 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulato
|
||||
X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \
|
||||
X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \
|
||||
X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \
|
||||
X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \
|
||||
X(a, STATIC, SINGULAR, BYTES, session_passkey, 101)
|
||||
#define meshtastic_AdminMessage_CALLBACK NULL
|
||||
#define meshtastic_AdminMessage_DEFAULT NULL
|
||||
|
||||
@@ -282,6 +282,12 @@ typedef enum _meshtastic_HardwareModel {
|
||||
meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 = 113,
|
||||
/* LilyGo T-Watch Ultra */
|
||||
meshtastic_HardwareModel_T_WATCH_ULTRA = 114,
|
||||
/* Elecrow ThinkNode M3 */
|
||||
meshtastic_HardwareModel_THINKNODE_M3 = 115,
|
||||
/* RAK WISMESH_TAP_V2 with ESP32-S3 CPU */
|
||||
meshtastic_HardwareModel_WISMESH_TAP_V2 = 116,
|
||||
/* RAK3401 */
|
||||
meshtastic_HardwareModel_RAK3401 = 117,
|
||||
/* ------------------------------------------------------------------------------------------------------------------------------------------
|
||||
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.
|
||||
------------------------------------------------------------------------------------------------------------------------------------------ */
|
||||
|
||||
@@ -101,7 +101,9 @@ typedef enum _meshtastic_TelemetrySensorType {
|
||||
/* SEN5X PM SENSORS */
|
||||
meshtastic_TelemetrySensorType_SEN5X = 43,
|
||||
/* TSL2561 light sensor */
|
||||
meshtastic_TelemetrySensorType_TSL2561 = 44
|
||||
meshtastic_TelemetrySensorType_TSL2561 = 44,
|
||||
/* BH1750 light sensor */
|
||||
meshtastic_TelemetrySensorType_BH1750 = 45
|
||||
} meshtastic_TelemetrySensorType;
|
||||
|
||||
/* Struct definitions */
|
||||
@@ -438,8 +440,8 @@ extern "C" {
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL2561
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL2561+1))
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BH1750
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BH1750+1))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -148,6 +148,8 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
|
||||
|
||||
void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
if (webServerThread)
|
||||
webServerThread->markActivity();
|
||||
|
||||
LOG_DEBUG("webAPI handleAPIv1FromRadio");
|
||||
|
||||
@@ -391,6 +393,9 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
void handleStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
if (webServerThread)
|
||||
webServerThread->markActivity();
|
||||
|
||||
// Get access to the parameters
|
||||
ResourceParameters *params = req->getParams();
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class HttpAPI : public PhoneAPI
|
||||
{
|
||||
|
||||
public:
|
||||
// Nothing here yet
|
||||
HttpAPI() { api_type = TYPE_HTTP; }
|
||||
|
||||
private:
|
||||
// Nothing here yet
|
||||
|
||||
@@ -49,6 +49,12 @@ Preferences prefs;
|
||||
using namespace httpsserver;
|
||||
#include "mesh/http/ContentHandler.h"
|
||||
|
||||
static const uint32_t ACTIVE_THRESHOLD_MS = 5000;
|
||||
static const uint32_t MEDIUM_THRESHOLD_MS = 30000;
|
||||
static const int32_t ACTIVE_INTERVAL_MS = 50;
|
||||
static const int32_t MEDIUM_INTERVAL_MS = 200;
|
||||
static const int32_t IDLE_INTERVAL_MS = 1000;
|
||||
|
||||
static SSLCert *cert;
|
||||
static HTTPSServer *secureServer;
|
||||
static HTTPServer *insecureServer;
|
||||
@@ -175,6 +181,32 @@ WebServerThread::WebServerThread() : concurrency::OSThread("WebServer")
|
||||
if (!config.network.wifi_enabled && !config.network.eth_enabled) {
|
||||
disable();
|
||||
}
|
||||
lastActivityTime = millis();
|
||||
}
|
||||
|
||||
void WebServerThread::markActivity()
|
||||
{
|
||||
lastActivityTime = millis();
|
||||
}
|
||||
|
||||
int32_t WebServerThread::getAdaptiveInterval()
|
||||
{
|
||||
uint32_t currentTime = millis();
|
||||
uint32_t timeSinceActivity;
|
||||
|
||||
if (currentTime >= lastActivityTime) {
|
||||
timeSinceActivity = currentTime - lastActivityTime;
|
||||
} else {
|
||||
timeSinceActivity = (UINT32_MAX - lastActivityTime) + currentTime + 1;
|
||||
}
|
||||
|
||||
if (timeSinceActivity < ACTIVE_THRESHOLD_MS) {
|
||||
return ACTIVE_INTERVAL_MS;
|
||||
} else if (timeSinceActivity < MEDIUM_THRESHOLD_MS) {
|
||||
return MEDIUM_INTERVAL_MS;
|
||||
} else {
|
||||
return IDLE_INTERVAL_MS;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t WebServerThread::runOnce()
|
||||
@@ -189,8 +221,7 @@ int32_t WebServerThread::runOnce()
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
// Loop every 5ms.
|
||||
return (5);
|
||||
return getAdaptiveInterval();
|
||||
}
|
||||
|
||||
void initWebServer()
|
||||
|
||||
@@ -10,13 +10,17 @@ void createSSLCert();
|
||||
|
||||
class WebServerThread : private concurrency::OSThread
|
||||
{
|
||||
private:
|
||||
uint32_t lastActivityTime = 0;
|
||||
|
||||
public:
|
||||
WebServerThread();
|
||||
uint32_t requestRestart = 0;
|
||||
void markActivity();
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
int32_t getAdaptiveInterval();
|
||||
};
|
||||
|
||||
extern WebServerThread *webServerThread;
|
||||
|
||||
@@ -27,7 +27,7 @@ class HttpAPI : public PhoneAPI
|
||||
{
|
||||
|
||||
public:
|
||||
// Nothing here yet
|
||||
HttpAPI() { api_type = TYPE_HTTP; }
|
||||
|
||||
private:
|
||||
// Nothing here yet
|
||||
|
||||
@@ -104,9 +104,18 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
(config.security.admin_key[2].size == 32 &&
|
||||
memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) {
|
||||
LOG_INFO("PKC admin payload with authorized sender key");
|
||||
|
||||
// Automatically favorite the node that is using the admin key
|
||||
auto remoteNode = nodeDB->getMeshNode(mp.from);
|
||||
if (remoteNode && !remoteNode->is_favorite) {
|
||||
remoteNode->is_favorite = true;
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
|
||||
// Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
|
||||
// without the user doing so deliberately.
|
||||
LOG_INFO("PKC admin valid, but not auto-favoriting node %x because role==CLIENT_BASE", mp.from);
|
||||
} else {
|
||||
LOG_INFO("PKC admin valid. Auto-favoriting node %x", mp.from);
|
||||
remoteNode->is_favorite = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp);
|
||||
@@ -280,7 +289,12 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
case meshtastic_AdminMessage_nodedb_reset_tag: {
|
||||
disableBluetooth();
|
||||
LOG_INFO("Initiate node-db reset");
|
||||
nodeDB->resetNodes();
|
||||
// CLIENT_BASE, ROUTER and ROUTER_LATE are able to preserve the remaining hop count when relaying a packet via a
|
||||
// favorited node, so ensure that their favorites are kept on reset
|
||||
bool rolePreference =
|
||||
isOneOf(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE,
|
||||
meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE);
|
||||
nodeDB->resetNodes(rolePreference ? rolePreference : r->nodedb_reset);
|
||||
reboot(DEFAULT_REBOOT_SECONDS);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -973,8 +973,14 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
|
||||
LOG_INFO("Send message id=%u, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes);
|
||||
|
||||
if (p->to != 0xffffffff) {
|
||||
LOG_INFO("Proactively adding %x as favorite node", p->to);
|
||||
nodeDB->set_favorite(true, p->to);
|
||||
// Only add as favorite if our role is NOT CLIENT_BASE
|
||||
if (config.device.role != 12) {
|
||||
LOG_INFO("Proactively adding %x as favorite node", p->to);
|
||||
nodeDB->set_favorite(true, p->to);
|
||||
} else {
|
||||
LOG_DEBUG("Not favoriting node %x as we are CLIENT_BASE role", p->to);
|
||||
}
|
||||
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||
p->pki_encrypted = true;
|
||||
p->channel = 0;
|
||||
|
||||
@@ -95,22 +95,9 @@ int32_t ExternalNotificationModule::runOnce()
|
||||
isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
|
||||
#endif
|
||||
if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
|
||||
// let the song finish if we reach timeout
|
||||
// Turn off external notification immediately when timeout is reached, regardless of song state
|
||||
nagCycleCutoff = UINT32_MAX;
|
||||
LOG_INFO("Turning off external notification: ");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
setExternalState(i, false);
|
||||
externalTurnedOn[i] = 0;
|
||||
LOG_INFO("%d ", i);
|
||||
}
|
||||
LOG_INFO("");
|
||||
#ifdef HAS_I2S
|
||||
// GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
|
||||
// T-Deck uses GPIO0 as trackball button, so restore the mode
|
||||
#if defined(T_DECK) || (defined(BUTTON_PIN) && BUTTON_PIN == 0)
|
||||
pinMode(0, INPUT);
|
||||
#endif
|
||||
#endif
|
||||
ExternalNotificationModule::stopNow();
|
||||
isNagging = false;
|
||||
return INT32_MAX; // save cycles till we're needed again
|
||||
}
|
||||
@@ -318,21 +305,36 @@ bool ExternalNotificationModule::nagging()
|
||||
|
||||
void ExternalNotificationModule::stopNow()
|
||||
{
|
||||
LOG_INFO("Turning off external notification: ");
|
||||
LOG_INFO("Stop RTTTL playback");
|
||||
rtttl::stop();
|
||||
#ifdef HAS_I2S
|
||||
LOG_INFO("Stop audioThread playback");
|
||||
if (audioThread->isPlaying())
|
||||
audioThread->stop();
|
||||
#endif
|
||||
nagCycleCutoff = 1; // small value
|
||||
isNagging = false;
|
||||
// Turn off all outputs
|
||||
LOG_INFO("Turning off setExternalStates: ");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
setExternalState(i, false);
|
||||
externalTurnedOn[i] = 0;
|
||||
LOG_INFO("%d ", i);
|
||||
}
|
||||
setIntervalFromNow(0);
|
||||
#ifdef T_WATCH_S3
|
||||
drv.stop();
|
||||
#endif
|
||||
|
||||
// Prevent the state machine from immediately re-triggering outputs after a manual stop.
|
||||
isNagging = false;
|
||||
nagCycleCutoff = UINT32_MAX;
|
||||
|
||||
#ifdef HAS_I2S
|
||||
// GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
|
||||
// T-Deck uses GPIO0 as trackball button, so restore the mode
|
||||
#if defined(T_DECK) || (defined(BUTTON_PIN) && BUTTON_PIN == 0)
|
||||
pinMode(0, INPUT);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
|
||||
LOG_DEBUG("---- Received Packet:");
|
||||
LOG_DEBUG("mp.from %d", mp.from);
|
||||
LOG_DEBUG("mp.rx_snr %f", mp.rx_snr);
|
||||
LOG_DEBUG("mp.rx_rssi %f", mp.rx_rssi);
|
||||
LOG_DEBUG("mp.hop_limit %d", mp.hop_limit);
|
||||
LOG_DEBUG("---- Node Information of Received Packet (mp.from):");
|
||||
LOG_DEBUG("n->user.long_name %s", n->user.long_name);
|
||||
@@ -234,8 +235,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
|
||||
}
|
||||
|
||||
// Print the CSV header
|
||||
if (fileToWrite.println(
|
||||
"time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload")) {
|
||||
if (fileToWrite.println("time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx "
|
||||
"snr,distance,hop limit,payload,rx rssi")) {
|
||||
LOG_INFO("File was written");
|
||||
} else {
|
||||
LOG_ERROR("File write failed");
|
||||
@@ -297,6 +298,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
|
||||
|
||||
// TODO: If quotes are found in the payload, it has to be escaped.
|
||||
fileToAppend.printf("\"%s\"\n", p.payload.bytes);
|
||||
fileToAppend.printf("%i,", mp.rx_rssi); // RX RSSI
|
||||
|
||||
fileToAppend.flush();
|
||||
fileToAppend.close();
|
||||
|
||||
|
||||
@@ -65,13 +65,22 @@ SerialModuleRadio *serialModuleRadio;
|
||||
|
||||
#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \
|
||||
defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE)
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {}
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial")
|
||||
{
|
||||
api_type = TYPE_SERIAL;
|
||||
}
|
||||
static Print *serialPrint = &Serial;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL)
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {}
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial")
|
||||
{
|
||||
api_type = TYPE_SERIAL;
|
||||
}
|
||||
static Print *serialPrint = &Serial1;
|
||||
#else
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial") {}
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial")
|
||||
{
|
||||
api_type = TYPE_SERIAL;
|
||||
}
|
||||
static Print *serialPrint = &Serial2;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -204,6 +204,10 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
|
||||
this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size;
|
||||
this->packetHistory[this->packetHistoryTotalCount].rx_rssi = mp.rx_rssi;
|
||||
this->packetHistory[this->packetHistoryTotalCount].rx_snr = mp.rx_snr;
|
||||
this->packetHistory[this->packetHistoryTotalCount].hop_start = mp.hop_start;
|
||||
this->packetHistory[this->packetHistoryTotalCount].hop_limit = mp.hop_limit;
|
||||
this->packetHistory[this->packetHistoryTotalCount].via_mqtt = mp.via_mqtt;
|
||||
this->packetHistory[this->packetHistoryTotalCount].transport_mechanism = mp.transport_mechanism;
|
||||
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
|
||||
|
||||
this->packetHistoryTotalCount++;
|
||||
@@ -256,6 +260,10 @@ meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t
|
||||
p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji;
|
||||
p->rx_rssi = this->packetHistory[i].rx_rssi;
|
||||
p->rx_snr = this->packetHistory[i].rx_snr;
|
||||
p->hop_start = this->packetHistory[i].hop_start;
|
||||
p->hop_limit = this->packetHistory[i].hop_limit;
|
||||
p->via_mqtt = this->packetHistory[i].via_mqtt;
|
||||
p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)this->packetHistory[i].transport_mechanism;
|
||||
|
||||
// Let's assume that if the server received the S&F request that the client is in range.
|
||||
// TODO: Make this configurable.
|
||||
|
||||
@@ -21,6 +21,10 @@ struct PacketHistoryStruct {
|
||||
pb_size_t payload_size;
|
||||
int32_t rx_rssi;
|
||||
float rx_snr;
|
||||
uint8_t hop_start;
|
||||
uint8_t hop_limit;
|
||||
bool via_mqtt;
|
||||
uint8_t transport_mechanism;
|
||||
};
|
||||
|
||||
class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<meshtastic_StoreAndForward>
|
||||
|
||||
@@ -85,10 +85,8 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event)
|
||||
switch (event->inputEvent) {
|
||||
// GPS
|
||||
case INPUT_BROKER_GPS_TOGGLE:
|
||||
LOG_WARN("GPS Toggle");
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
if (gps) {
|
||||
LOG_WARN("GPS Toggle2");
|
||||
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED &&
|
||||
config.position.fixed_position == false) {
|
||||
nodeDB->clearLocalPosition();
|
||||
|
||||
@@ -361,6 +361,7 @@ bool EnvironmentTelemetryModule::wantUIFrame()
|
||||
return moduleConfig.telemetry.environment_screen_enabled;
|
||||
}
|
||||
|
||||
#if HAS_SCREEN
|
||||
void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// === Setup display ===
|
||||
@@ -509,7 +510,9 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
|
||||
|
||||
currentY += rowHeight;
|
||||
}
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
|
||||
{
|
||||
|
||||
@@ -108,6 +108,7 @@ bool PowerTelemetryModule::wantUIFrame()
|
||||
return moduleConfig.telemetry.power_screen_enabled;
|
||||
}
|
||||
|
||||
#if HAS_SCREEN
|
||||
void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->clear();
|
||||
@@ -164,7 +165,9 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
|
||||
if (m.has_ch3_voltage || m.has_ch3_current) {
|
||||
drawLine("Ch3", m.ch3_voltage, m.ch3_current);
|
||||
}
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,113 @@ extern graphics::Screen *screen;
|
||||
|
||||
TraceRouteModule *traceRouteModule;
|
||||
|
||||
void TraceRouteModule::setResultText(const String &text)
|
||||
{
|
||||
resultText = text;
|
||||
resultLines.clear();
|
||||
resultLinesDirty = true;
|
||||
}
|
||||
|
||||
void TraceRouteModule::clearResultLines()
|
||||
{
|
||||
resultLines.clear();
|
||||
resultLinesDirty = false;
|
||||
}
|
||||
#if HAS_SCREEN
|
||||
void TraceRouteModule::rebuildResultLines(OLEDDisplay *display)
|
||||
{
|
||||
if (!display) {
|
||||
resultLinesDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
resultLines.clear();
|
||||
|
||||
if (resultText.length() == 0) {
|
||||
resultLinesDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int maxWidth = display->getWidth() - 4;
|
||||
if (maxWidth <= 0) {
|
||||
resultLinesDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int start = 0;
|
||||
int textLength = resultText.length();
|
||||
|
||||
while (start <= textLength) {
|
||||
int newlinePos = resultText.indexOf('\n', start);
|
||||
String segment;
|
||||
|
||||
if (newlinePos != -1) {
|
||||
segment = resultText.substring(start, newlinePos);
|
||||
start = newlinePos + 1;
|
||||
} else {
|
||||
segment = resultText.substring(start);
|
||||
start = textLength + 1;
|
||||
}
|
||||
|
||||
if (segment.length() == 0) {
|
||||
resultLines.push_back("");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (display->getStringWidth(segment) <= maxWidth) {
|
||||
resultLines.push_back(segment);
|
||||
continue;
|
||||
}
|
||||
|
||||
String remaining = segment;
|
||||
|
||||
while (remaining.length() > 0) {
|
||||
String tempLine = "";
|
||||
int lastGoodBreak = -1;
|
||||
bool lineComplete = false;
|
||||
|
||||
for (int i = 0; i < static_cast<int>(remaining.length()); i++) {
|
||||
char ch = remaining.charAt(i);
|
||||
String testLine = tempLine + ch;
|
||||
|
||||
if (display->getStringWidth(testLine) > maxWidth) {
|
||||
if (lastGoodBreak >= 0) {
|
||||
resultLines.push_back(remaining.substring(0, lastGoodBreak + 1));
|
||||
remaining = remaining.substring(lastGoodBreak + 1);
|
||||
lineComplete = true;
|
||||
break;
|
||||
} else if (tempLine.length() > 0) {
|
||||
resultLines.push_back(tempLine);
|
||||
remaining = remaining.substring(i);
|
||||
lineComplete = true;
|
||||
break;
|
||||
} else {
|
||||
resultLines.push_back(String(ch));
|
||||
remaining = remaining.substring(i + 1);
|
||||
lineComplete = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
tempLine = testLine;
|
||||
if (ch == ' ' || ch == '>' || ch == '<' || ch == '-' || ch == '(' || ch == ')' || ch == ',') {
|
||||
lastGoodBreak = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!lineComplete) {
|
||||
if (tempLine.length() > 0) {
|
||||
resultLines.push_back(tempLine);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resultLinesDirty = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r)
|
||||
{
|
||||
// We only alter the packet in alterReceivedProtobuf()
|
||||
@@ -21,6 +128,11 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
|
||||
{
|
||||
const meshtastic_Data &incoming = p.decoded;
|
||||
|
||||
// Update next-hops using returned route
|
||||
if (incoming.request_id) {
|
||||
updateNextHops(p, r);
|
||||
}
|
||||
|
||||
// Insert unknown hops if necessary
|
||||
insertUnknownHops(p, r, !incoming.request_id);
|
||||
|
||||
@@ -153,6 +265,65 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
|
||||
}
|
||||
}
|
||||
|
||||
void TraceRouteModule::updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r)
|
||||
{
|
||||
// E.g. if the route is A->B->C->D and we are B, we can set C as next-hop for C and D
|
||||
// Similarly, if we are C, we can set D as next-hop for D
|
||||
// If we are A, we can set B as next-hop for B, C and D
|
||||
|
||||
// First check if we were the original sender or in the original route
|
||||
int8_t nextHopIndex = -1;
|
||||
if (isToUs(&p)) {
|
||||
nextHopIndex = 0; // We are the original sender, next hop is first in route
|
||||
} else {
|
||||
// Check if we are in the original route
|
||||
for (uint8_t i = 0; i < r->route_count; i++) {
|
||||
if (r->route[i] == nodeDB->getNodeNum()) {
|
||||
nextHopIndex = i + 1; // Next hop is the one after us
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we are in the original route, update the next hops
|
||||
if (nextHopIndex != -1) {
|
||||
// For every node after us, we can set the next-hop to the first node after us
|
||||
NodeNum nextHop;
|
||||
if (nextHopIndex == r->route_count) {
|
||||
nextHop = p.from; // We are the last in the route, next hop is destination
|
||||
} else {
|
||||
nextHop = r->route[nextHopIndex];
|
||||
}
|
||||
|
||||
if (nextHop == NODENUM_BROADCAST) {
|
||||
return;
|
||||
}
|
||||
uint8_t nextHopByte = nodeDB->getLastByteOfNodeNum(nextHop);
|
||||
|
||||
// For the rest of the nodes in the route, set their next-hop
|
||||
// Note: if we are the last in the route, this loop will not run
|
||||
for (int8_t i = nextHopIndex; i < r->route_count; i++) {
|
||||
NodeNum targetNode = r->route[i];
|
||||
maybeSetNextHop(targetNode, nextHopByte);
|
||||
}
|
||||
|
||||
// Also set next-hop for the destination node
|
||||
maybeSetNextHop(p.from, nextHopByte);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceRouteModule::maybeSetNextHop(NodeNum target, uint8_t nextHopByte)
|
||||
{
|
||||
if (target == NODENUM_BROADCAST)
|
||||
return;
|
||||
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(target);
|
||||
if (node && node->next_hop != nextHopByte) {
|
||||
LOG_INFO("Updating next-hop for 0x%08x to 0x%02x based on traceroute", target, nextHopByte);
|
||||
node->next_hop = nextHopByte;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceRouteModule::processUpgradedPacket(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag || mp.decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP)
|
||||
@@ -342,7 +513,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
|
||||
if (node == 0 || node == NODENUM_BROADCAST) {
|
||||
LOG_ERROR("Invalid node number for trace route: 0x%08x", node);
|
||||
runState = TRACEROUTE_STATE_RESULT;
|
||||
resultText = "Invalid node";
|
||||
setResultText("Invalid node");
|
||||
resultShowTime = millis();
|
||||
tracingNode = 0;
|
||||
|
||||
@@ -356,7 +527,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
|
||||
if (node == nodeDB->getNodeNum()) {
|
||||
LOG_ERROR("Cannot trace route to self: 0x%08x", node);
|
||||
runState = TRACEROUTE_STATE_RESULT;
|
||||
resultText = "Cannot trace self";
|
||||
setResultText("Cannot trace self");
|
||||
resultShowTime = millis();
|
||||
tracingNode = 0;
|
||||
|
||||
@@ -383,6 +554,8 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
|
||||
unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
|
||||
bannerText = String("Wait for ") + String(wait) + String("s");
|
||||
runState = TRACEROUTE_STATE_COOLDOWN;
|
||||
resultText = "";
|
||||
clearResultLines();
|
||||
|
||||
requestFocus();
|
||||
UIFrameEvent e;
|
||||
@@ -395,6 +568,8 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
|
||||
tracingNode = node;
|
||||
lastTraceRouteTime = now;
|
||||
runState = TRACEROUTE_STATE_TRACKING;
|
||||
resultText = "";
|
||||
clearResultLines();
|
||||
bannerText = String("Tracing ") + getNodeName(node);
|
||||
|
||||
LOG_INFO("TraceRoute UI: Starting trace route to node 0x%08x, requesting focus", node);
|
||||
@@ -437,7 +612,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
|
||||
} else {
|
||||
LOG_ERROR("MeshService is NULL!");
|
||||
runState = TRACEROUTE_STATE_RESULT;
|
||||
resultText = "Service unavailable";
|
||||
setResultText("Service unavailable");
|
||||
resultShowTime = millis();
|
||||
tracingNode = 0;
|
||||
|
||||
@@ -450,7 +625,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
|
||||
} else {
|
||||
LOG_ERROR("Failed to allocate TraceRoute packet from router");
|
||||
runState = TRACEROUTE_STATE_RESULT;
|
||||
resultText = "Failed to send";
|
||||
setResultText("Failed to send");
|
||||
resultShowTime = millis();
|
||||
tracingNode = 0;
|
||||
|
||||
@@ -468,7 +643,7 @@ void TraceRouteModule::launch(NodeNum node)
|
||||
if (node == 0 || node == NODENUM_BROADCAST) {
|
||||
LOG_ERROR("Invalid node number for trace route: 0x%08x", node);
|
||||
runState = TRACEROUTE_STATE_RESULT;
|
||||
resultText = "Invalid node";
|
||||
setResultText("Invalid node");
|
||||
resultShowTime = millis();
|
||||
tracingNode = 0;
|
||||
|
||||
@@ -482,7 +657,7 @@ void TraceRouteModule::launch(NodeNum node)
|
||||
if (node == nodeDB->getNodeNum()) {
|
||||
LOG_ERROR("Cannot trace route to self: 0x%08x", node);
|
||||
runState = TRACEROUTE_STATE_RESULT;
|
||||
resultText = "Cannot trace self";
|
||||
setResultText("Cannot trace self");
|
||||
resultShowTime = millis();
|
||||
tracingNode = 0;
|
||||
|
||||
@@ -504,6 +679,8 @@ void TraceRouteModule::launch(NodeNum node)
|
||||
unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
|
||||
bannerText = String("Wait for ") + String(wait) + String("s");
|
||||
runState = TRACEROUTE_STATE_COOLDOWN;
|
||||
resultText = "";
|
||||
clearResultLines();
|
||||
|
||||
requestFocus();
|
||||
UIFrameEvent e;
|
||||
@@ -516,6 +693,8 @@ void TraceRouteModule::launch(NodeNum node)
|
||||
runState = TRACEROUTE_STATE_TRACKING;
|
||||
tracingNode = node;
|
||||
lastTraceRouteTime = now;
|
||||
resultText = "";
|
||||
clearResultLines();
|
||||
bannerText = String("Tracing ") + getNodeName(node);
|
||||
|
||||
requestFocus();
|
||||
@@ -550,14 +729,14 @@ void TraceRouteModule::launch(NodeNum node)
|
||||
} else {
|
||||
LOG_ERROR("MeshService is NULL!");
|
||||
runState = TRACEROUTE_STATE_RESULT;
|
||||
resultText = "Service unavailable";
|
||||
setResultText("Service unavailable");
|
||||
resultShowTime = millis();
|
||||
tracingNode = 0;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("Failed to allocate TraceRoute packet from router");
|
||||
runState = TRACEROUTE_STATE_RESULT;
|
||||
resultText = "Failed to send";
|
||||
setResultText("Failed to send");
|
||||
resultShowTime = millis();
|
||||
tracingNode = 0;
|
||||
}
|
||||
@@ -565,7 +744,7 @@ void TraceRouteModule::launch(NodeNum node)
|
||||
|
||||
void TraceRouteModule::handleTraceRouteResult(const String &result)
|
||||
{
|
||||
resultText = result;
|
||||
setResultText(result);
|
||||
runState = TRACEROUTE_STATE_RESULT;
|
||||
resultShowTime = millis();
|
||||
tracingNode = 0;
|
||||
@@ -615,83 +794,15 @@ void TraceRouteModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
if (resultText.length() > 0) {
|
||||
std::vector<String> lines;
|
||||
String currentLine = "";
|
||||
int maxWidth = display->getWidth() - 4;
|
||||
|
||||
int start = 0;
|
||||
int newlinePos = resultText.indexOf('\n', start);
|
||||
|
||||
while (newlinePos != -1 || start < static_cast<int>(resultText.length())) {
|
||||
String segment;
|
||||
if (newlinePos != -1) {
|
||||
segment = resultText.substring(start, newlinePos);
|
||||
start = newlinePos + 1;
|
||||
newlinePos = resultText.indexOf('\n', start);
|
||||
} else {
|
||||
segment = resultText.substring(start);
|
||||
start = resultText.length();
|
||||
}
|
||||
|
||||
if (display->getStringWidth(segment) <= maxWidth) {
|
||||
lines.push_back(segment);
|
||||
} else {
|
||||
// Try to break at better positions (space, >, <, -)
|
||||
String remaining = segment;
|
||||
|
||||
while (remaining.length() > 0) {
|
||||
String tempLine = "";
|
||||
int lastGoodBreak = -1;
|
||||
bool lineComplete = false;
|
||||
|
||||
for (int i = 0; i < static_cast<int>(remaining.length()); i++) {
|
||||
char ch = remaining.charAt(i);
|
||||
String testLine = tempLine + ch;
|
||||
|
||||
if (display->getStringWidth(testLine) > maxWidth) {
|
||||
if (lastGoodBreak >= 0) {
|
||||
// Break at the last good position
|
||||
lines.push_back(remaining.substring(0, lastGoodBreak + 1));
|
||||
remaining = remaining.substring(lastGoodBreak + 1);
|
||||
lineComplete = true;
|
||||
break;
|
||||
} else if (tempLine.length() > 0) {
|
||||
lines.push_back(tempLine);
|
||||
remaining = remaining.substring(i);
|
||||
lineComplete = true;
|
||||
break;
|
||||
} else {
|
||||
// Single character exceeds width
|
||||
lines.push_back(String(ch));
|
||||
remaining = remaining.substring(i + 1);
|
||||
lineComplete = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
tempLine = testLine;
|
||||
// Mark good break positions
|
||||
if (ch == ' ' || ch == '>' || ch == '<' || ch == '-' || ch == '(' || ch == ')') {
|
||||
lastGoodBreak = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!lineComplete) {
|
||||
// Reached end of remaining text
|
||||
if (tempLine.length() > 0) {
|
||||
lines.push_back(tempLine);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resultLinesDirty) {
|
||||
rebuildResultLines(display);
|
||||
}
|
||||
|
||||
int lineHeight = FONT_HEIGHT_SMALL + 1; // Use proper font height with 1px spacing
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
for (size_t i = 0; i < resultLines.size(); i++) {
|
||||
int lineY = contentStartY + (i * lineHeight);
|
||||
if (lineY + FONT_HEIGHT_SMALL <= display->getHeight()) {
|
||||
display->drawString(x + 2, lineY, lines[i]);
|
||||
display->drawString(x + 2, lineY, resultLines[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -715,7 +826,7 @@ int32_t TraceRouteModule::runOnce()
|
||||
if (runState == TRACEROUTE_STATE_TRACKING && now - lastTraceRouteTime > trackingTimeoutMs) {
|
||||
LOG_INFO("TraceRoute timeout, no response received");
|
||||
runState = TRACEROUTE_STATE_RESULT;
|
||||
resultText = "No response received";
|
||||
setResultText("No response received");
|
||||
resultShowTime = now;
|
||||
tracingNode = 0;
|
||||
|
||||
@@ -751,6 +862,8 @@ int32_t TraceRouteModule::runOnce()
|
||||
// Cooldown finished
|
||||
LOG_INFO("TraceRoute cooldown finished, returning to IDLE");
|
||||
runState = TRACEROUTE_STATE_IDLE;
|
||||
resultText = "";
|
||||
clearResultLines();
|
||||
bannerText = "";
|
||||
UIFrameEvent e;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
|
||||
@@ -764,6 +877,7 @@ int32_t TraceRouteModule::runOnce()
|
||||
LOG_INFO("TraceRoute result display timeout, returning to IDLE");
|
||||
runState = TRACEROUTE_STATE_IDLE;
|
||||
resultText = "";
|
||||
clearResultLines();
|
||||
bannerText = "";
|
||||
tracingNode = 0;
|
||||
UIFrameEvent e;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#if HAS_SCREEN
|
||||
#include "OLEDDisplayUi.h"
|
||||
#endif
|
||||
#include <vector>
|
||||
|
||||
#define ROUTE_SIZE sizeof(((meshtastic_RouteDiscovery *)0)->route) / sizeof(((meshtastic_RouteDiscovery *)0)->route[0])
|
||||
|
||||
@@ -49,12 +50,23 @@ class TraceRouteModule : public ProtobufModule<meshtastic_RouteDiscovery>,
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
void setResultText(const String &text);
|
||||
void clearResultLines();
|
||||
#if HAS_SCREEN
|
||||
void rebuildResultLines(OLEDDisplay *display);
|
||||
#endif
|
||||
// Call to add unknown hops (e.g. when a node couldn't decrypt it) to the route based on hopStart and current hopLimit
|
||||
void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination);
|
||||
|
||||
// Call to add your ID to the route array of a RouteDiscovery message
|
||||
void appendMyIDandSNR(meshtastic_RouteDiscovery *r, float snr, bool isTowardsDestination, bool SNRonly);
|
||||
|
||||
// Update next-hops in the routing table based on the returned route
|
||||
void updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r);
|
||||
|
||||
// Helper to update next-hop for a single node
|
||||
void maybeSetNextHop(NodeNum target, uint8_t nextHopByte);
|
||||
|
||||
/* Call to print the route array of a RouteDiscovery message.
|
||||
Set origin to where the request came from.
|
||||
Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */
|
||||
@@ -68,6 +80,8 @@ class TraceRouteModule : public ProtobufModule<meshtastic_RouteDiscovery>,
|
||||
unsigned long trackingTimeoutMs = 10000;
|
||||
String bannerText;
|
||||
String resultText;
|
||||
std::vector<String> resultLines;
|
||||
bool resultLinesDirty = false;
|
||||
NodeNum tracingNode = 0;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
@@ -141,6 +141,7 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
display->drawStringf(display->getWidth() / 2 + x, graphics::getTextPositions(display)[line++], buffer,
|
||||
"WiFi: %d\nBLE: %d\nUptime: %ds", count_from_libpax.wifi_count, count_from_libpax.ble_count,
|
||||
millis() / 1000);
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
#endif // HAS_SCREEN
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ void BMX160Sensor::calibrate(uint16_t forSeconds)
|
||||
{
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
LOG_DEBUG("BMX160 calibration started for %is", forSeconds);
|
||||
highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
|
||||
|
||||
doCalibration = true;
|
||||
uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided
|
||||
@@ -127,4 +128,4 @@ void BMX160Sensor::calibrate(uint16_t forSeconds)
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -157,6 +157,7 @@ void ICM20948Sensor::calibrate(uint16_t forSeconds)
|
||||
{
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN
|
||||
LOG_DEBUG("BMX160 calibration started for %is", forSeconds);
|
||||
highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
|
||||
|
||||
doCalibration = true;
|
||||
uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided
|
||||
@@ -295,4 +296,4 @@ bool ICM20948Singleton::setWakeOnMotion()
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -69,7 +69,8 @@ void MotionSensor::wakeScreen()
|
||||
{
|
||||
if (powerFSM.getState() == &stateDARK) {
|
||||
LOG_DEBUG("Motion wakeScreen detected");
|
||||
powerFSM.trigger(EVENT_INPUT);
|
||||
if (config.display.wake_on_tap_or_motion)
|
||||
powerFSM.trigger(EVENT_INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,4 +88,4 @@ void MotionSensor::buttonPress() {}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -51,6 +51,7 @@ constexpr int reconnectMax = 5;
|
||||
static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid
|
||||
|
||||
static bool isMqttServerAddressPrivate = false;
|
||||
static bool isConnected = false;
|
||||
|
||||
inline void onReceiveProto(char *topic, byte *payload, size_t length)
|
||||
{
|
||||
@@ -59,7 +60,27 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
|
||||
LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!", topic, length);
|
||||
return;
|
||||
}
|
||||
|
||||
const meshtastic_Channel &ch = channels.getByName(e.channel_id);
|
||||
// Find channel by channel_id and check downlink_enabled
|
||||
if (!(strcmp(e.channel_id, "PKI") == 0 ||
|
||||
(strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool anyChannelHasDownlink = false;
|
||||
size_t numChan = channels.getNumChannels();
|
||||
for (size_t i = 0; i < numChan; ++i) {
|
||||
const auto &c = channels.getByIndex(i);
|
||||
if (c.settings.downlink_enabled) {
|
||||
anyChannelHasDownlink = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(e.channel_id, "PKI") == 0 && !anyChannelHasDownlink) {
|
||||
return;
|
||||
}
|
||||
// Generate node ID from nodenum for comparison
|
||||
std::string nodeId = nodeDB->getNodeId();
|
||||
if (strcmp(e.gateway_id, nodeId.c_str()) == 0) {
|
||||
@@ -77,11 +98,6 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
|
||||
return;
|
||||
}
|
||||
|
||||
// Find channel by channel_id and check downlink_enabled
|
||||
if (!(strcmp(e.channel_id, "PKI") == 0 ||
|
||||
(strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
|
||||
return;
|
||||
}
|
||||
LOG_INFO("Received MQTT topic %s, len=%u", topic, length);
|
||||
if (e.packet->hop_limit > HOP_MAX || e.packet->hop_start > HOP_MAX) {
|
||||
LOG_INFO("Invalid hop_limit(%u) or hop_start(%u)", e.packet->hop_limit, e.packet->hop_start);
|
||||
@@ -305,8 +321,10 @@ bool connectPubSub(const PubSubConfig &config, PubSubClient &pubSub, Client &cli
|
||||
std::string nodeId = nodeDB->getNodeId();
|
||||
const bool connected = pubSub.connect(nodeId.c_str(), config.mqttUsername, config.mqttPassword);
|
||||
if (connected) {
|
||||
isConnected = true;
|
||||
LOG_INFO("MQTT connected");
|
||||
} else {
|
||||
isConnected = false;
|
||||
LOG_WARN("Failed to connect to MQTT server");
|
||||
}
|
||||
return connected;
|
||||
@@ -492,6 +510,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo
|
||||
|
||||
void MQTT::reconnect()
|
||||
{
|
||||
isConnected = false;
|
||||
if (wantsLink()) {
|
||||
if (moduleConfig.mqtt.proxy_to_client_enabled) {
|
||||
LOG_INFO("MQTT connect via client proxy instead");
|
||||
@@ -519,7 +538,7 @@ void MQTT::reconnect()
|
||||
runASAP = true;
|
||||
reconnectCount = 0;
|
||||
isMqttServerAddressPrivate = isPrivateIpAddress(clientConnection->remoteIP());
|
||||
|
||||
isConnected = true;
|
||||
publishNodeInfo();
|
||||
sendSubscriptions();
|
||||
} else {
|
||||
@@ -673,7 +692,10 @@ void MQTT::publishNodeInfo()
|
||||
}
|
||||
void MQTT::publishQueuedMessages()
|
||||
{
|
||||
if (mqttQueue.isEmpty())
|
||||
if (mqttQueue.isEmpty() || !isConnected)
|
||||
return;
|
||||
|
||||
if (!moduleConfig.mqtt.proxy_to_client_enabled && !isConnected)
|
||||
return;
|
||||
|
||||
LOG_DEBUG("Publish enqueued MQTT message");
|
||||
@@ -880,4 +902,4 @@ void MQTT::perhapsReportToMap()
|
||||
|
||||
// Update the last report time
|
||||
last_report_to_map = millis();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user