Compare commits
198
Commits
v2.7.12.45f15b8
...
beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80b7dcba11 | ||
|
|
2438b5d813 | ||
|
|
314fed7e03 | ||
|
|
6c9505885d | ||
|
|
aec497bb21 | ||
|
|
8b62be9d3a | ||
|
|
d4e61f784a | ||
|
|
9dbf2f05af | ||
|
|
0554451103 | ||
|
|
c554785aa0 | ||
|
|
64cda52920 | ||
|
|
c4c4fe7082 | ||
|
|
95a9135f26 | ||
|
|
5e9d0de9ac | ||
|
|
710fb010b4 | ||
|
|
4f842e3597 | ||
|
|
b2ab6baa3b | ||
|
|
778f93e4a0 | ||
|
|
f377d6d405 | ||
|
|
676aae05d8 | ||
|
|
9c5540407e | ||
|
|
49057e8eb7 | ||
|
|
e708ec073c | ||
|
|
f3671a2c06 | ||
|
|
573677f352 | ||
|
|
42e56ae926 | ||
|
|
d2539956a9 | ||
|
|
cb6e8be034 | ||
|
|
bcd3e9f650 | ||
|
|
1b2b1b785f | ||
|
|
1e93126888 | ||
|
|
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 | ||
|
+1 |
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 | ||
|
|
32ebc70bca | ||
|
|
5953b4704e | ||
|
|
a34c584028 | ||
|
|
865b46ceef | ||
|
|
cd3b31c5da | ||
|
|
51b3b937dc | ||
|
|
ec5a54c523 | ||
|
|
4e0a4cc45f | ||
|
|
858e8c6fef | ||
|
|
a6df18e60a | ||
|
|
9a8aeb25ab | ||
|
|
e8f4d07e9f | ||
|
|
034d2dd025 | ||
|
|
b8bfed2810 | ||
|
|
dbb439f121 | ||
|
|
c4d7ad2190 | ||
|
|
5814f3e7d2 | ||
|
|
910fe911f8 | ||
|
|
9ab9650248 | ||
|
|
37a0f774a2 | ||
|
|
a71b47b5bb | ||
|
|
9df5aa8c70 | ||
|
|
130833b5be | ||
|
|
fe6509a0f2 | ||
|
|
1212c2c11b | ||
|
|
fcaa168d2d | ||
|
|
e24e2ccf62 | ||
|
|
7537d28419 | ||
|
|
26f25069dd | ||
|
|
5eeffdb290 | ||
|
|
5d71776527 | ||
|
|
661e596dbb | ||
|
|
a6732682de | ||
|
|
f0126d44e2 | ||
|
|
fb08e17c39 | ||
|
|
11aff46af1 | ||
|
|
cb11e6b720 | ||
|
|
464663b496 | ||
|
|
981d058e9f | ||
|
|
9056915e7b | ||
|
|
554112ceb5 | ||
|
|
29458cd8c4 | ||
|
|
8bf32dc042 | ||
|
|
73cadce581 | ||
|
|
eee80ce636 | ||
|
|
30d6962e79 | ||
|
|
f99747180e | ||
|
|
91d928d4c5 | ||
|
|
64bfe73c06 | ||
|
|
fca5343460 | ||
|
|
cafb007ec4 | ||
|
|
694b669eb7 | ||
|
|
7899340131 | ||
|
|
0bb1c1fe6f | ||
|
|
05febc25e1 | ||
|
|
e5a2ce54e7 | ||
|
|
fe2e2753aa | ||
|
|
9c5513dcfe | ||
|
|
e8e8ee0993 | ||
|
|
a7f15097da | ||
|
|
e4ec719e6f |
@@ -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": "latest"
|
||||
"version": "3.14"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -88,62 +88,6 @@ jobs:
|
||||
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
|
||||
docker-deb-amd64:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-deb-amd64-tft:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
pio_env: native-tft
|
||||
|
||||
docker-alp-amd64:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-alp-amd64-tft:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
pio_env: native-tft
|
||||
|
||||
docker-deb-arm64:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: false
|
||||
|
||||
docker-deb-armv7:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm/v7
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: false
|
||||
|
||||
gather-artifacts:
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -169,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}}-*
|
||||
@@ -182,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
|
||||
@@ -198,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
|
||||
@@ -217,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
|
||||
|
||||
@@ -106,62 +106,6 @@ jobs:
|
||||
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }}
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
|
||||
docker-deb-amd64:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-deb-amd64-tft:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
pio_env: native-tft
|
||||
|
||||
docker-alp-amd64:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-alp-amd64-tft:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
pio_env: native-tft
|
||||
|
||||
docker-deb-arm64:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: false
|
||||
|
||||
docker-deb-armv7:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm/v7
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: false
|
||||
|
||||
gather-artifacts:
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -175,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-*-*
|
||||
@@ -188,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
|
||||
@@ -204,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
|
||||
@@ -223,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
|
||||
|
||||
@@ -27,7 +27,6 @@ on:
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
@@ -42,10 +41,6 @@ jobs:
|
||||
python-version: 3.x
|
||||
cache: pip
|
||||
- run: pip install -U platformio
|
||||
- name: Uncomment build epoch
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
|
||||
- name: Generate matrix
|
||||
id: jsonStep
|
||||
run: |
|
||||
@@ -62,7 +57,6 @@ jobs:
|
||||
check: ${{ steps.jsonStep.outputs.check }}
|
||||
|
||||
version:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
@@ -125,60 +119,26 @@ jobs:
|
||||
if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }}
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
|
||||
docker-deb-amd64:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
docker:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro: [debian, alpine]
|
||||
platform: [linux/amd64, linux/arm64, linux/arm/v7]
|
||||
pio_env: [native, native-tft]
|
||||
exclude:
|
||||
- distro: alpine
|
||||
platform: linux/arm/v7
|
||||
- pio_env: native-tft
|
||||
platform: linux/arm64
|
||||
- pio_env: native-tft
|
||||
platform: linux/arm/v7
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-deb-amd64-tft:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
pio_env: native-tft
|
||||
|
||||
docker-alp-amd64:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-alp-amd64-tft:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
pio_env: native-tft
|
||||
|
||||
docker-deb-arm64:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: false
|
||||
|
||||
docker-deb-armv7:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm/v7
|
||||
runs-on: ubuntu-24.04-arm
|
||||
distro: ${{ matrix.distro }}
|
||||
platform: ${{ matrix.platform }}
|
||||
runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
pio_env: ${{ matrix.pio_env }}
|
||||
push: false
|
||||
|
||||
gather-artifacts:
|
||||
@@ -208,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}}-*
|
||||
@@ -221,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
|
||||
@@ -237,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
|
||||
@@ -256,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
|
||||
@@ -301,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
|
||||
@@ -358,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
|
||||
@@ -375,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
|
||||
@@ -413,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
|
||||
|
||||
@@ -99,54 +99,26 @@ jobs:
|
||||
if: ${{ !contains(github.ref_name, 'event/') }}
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
|
||||
docker-deb-amd64:
|
||||
docker:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro: [debian, alpine]
|
||||
platform: [linux/amd64, linux/arm64, linux/arm/v7]
|
||||
pio_env: [native, native-tft]
|
||||
exclude:
|
||||
- distro: alpine
|
||||
platform: linux/arm/v7
|
||||
- pio_env: native-tft
|
||||
platform: linux/arm64
|
||||
- pio_env: native-tft
|
||||
platform: linux/arm/v7
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-deb-amd64-tft:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
pio_env: native-tft
|
||||
|
||||
docker-alp-amd64:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-alp-amd64-tft:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
pio_env: native-tft
|
||||
|
||||
docker-deb-arm64:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: false
|
||||
|
||||
docker-deb-armv7:
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm/v7
|
||||
runs-on: ubuntu-24.04-arm
|
||||
distro: ${{ matrix.distro }}
|
||||
platform: ${{ matrix.platform }}
|
||||
runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
pio_env: ${{ matrix.pio_env }}
|
||||
push: false
|
||||
|
||||
gather-artifacts:
|
||||
@@ -175,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}}-*
|
||||
@@ -188,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
|
||||
@@ -204,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
|
||||
@@ -223,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
|
||||
@@ -268,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
|
||||
@@ -325,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
|
||||
@@ -342,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
|
||||
@@ -380,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,5 +20,7 @@ jobs:
|
||||
uses: actions/stale@v10.1.0
|
||||
with:
|
||||
days-before-stale: 45
|
||||
exempt-issue-labels: pinned,3.0
|
||||
exempt-pr-labels: pinned,3.0
|
||||
stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
|
||||
close-issue-message: This issue has not had any comment since the last notice. It has been closed automatically. If this is incorrect, or the issue becomes relevant again, please request that it is reopened.
|
||||
exempt-issue-labels: pinned,3.0,triaged,backlog
|
||||
exempt-pr-labels: pinned,3.0,triaged,backlog
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -47,9 +47,9 @@ jobs:
|
||||
pio upgrade
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
+10
-10
@@ -4,31 +4,31 @@ cli:
|
||||
plugins:
|
||||
sources:
|
||||
- id: trunk
|
||||
ref: v1.7.2
|
||||
ref: v1.7.3
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.473
|
||||
- renovate@41.132.5
|
||||
- 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.0
|
||||
- trivy@0.67.2
|
||||
- taplo@0.10.0
|
||||
- ruff@0.13.3
|
||||
- isort@6.1.0
|
||||
- ruff@0.14.4
|
||||
- isort@7.0.0
|
||||
- markdownlint@0.45.0
|
||||
- oxipng@9.1.5
|
||||
- svgo@4.0.0
|
||||
- actionlint@1.7.7
|
||||
- actionlint@1.7.8
|
||||
- flake8@7.3.0
|
||||
- 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]
|
||||
|
||||
+4
-3
@@ -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 \
|
||||
|
||||
@@ -31,6 +31,7 @@ build_flags =
|
||||
-DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL
|
||||
-DAXP_DEBUG_PORT=Serial
|
||||
-DCONFIG_BT_NIMBLE_ENABLED
|
||||
-DCONFIG_BT_NIMBLE_MAX_BONDS=6 # default is 3
|
||||
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
|
||||
-DCONFIG_BT_NIMBLE_MAX_CCCDS=20
|
||||
-DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
|
||||
@@ -56,7 +57,7 @@ lib_deps =
|
||||
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
|
||||
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
|
||||
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
|
||||
https://github.com/lewisxhe/XPowersLib/archive/v0.3.0.zip
|
||||
https://github.com/lewisxhe/XPowersLib/archive/v0.3.1.zip
|
||||
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
|
||||
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
|
||||
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
|
||||
|
||||
@@ -28,7 +28,7 @@ lib_deps =
|
||||
${environmental_extra.lib_deps}
|
||||
${radiolib_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
|
||||
lewisxhe/XPowersLib@0.3.0
|
||||
lewisxhe/XPowersLib@0.3.1
|
||||
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
|
||||
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
|
||||
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
|
||||
|
||||
@@ -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
|
||||
|
||||
+11
-9
@@ -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"
|
||||
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to cancel all running GitHub Actions workflows
|
||||
# Requires GitHub CLI (gh) to be installed and authenticated
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if gh CLI is installed
|
||||
if ! command -v gh &> /dev/null; then
|
||||
print_error "GitHub CLI (gh) is not installed. Please install it first:"
|
||||
echo " brew install gh"
|
||||
echo " Or visit: https://cli.github.com/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if authenticated
|
||||
if ! gh auth status &> /dev/null; then
|
||||
print_error "GitHub CLI is not authenticated. Please run:"
|
||||
echo " gh auth login"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get repository info
|
||||
REPO=$(gh repo view --json owner,name -q '.owner.login + "/" + .name')
|
||||
if [[ -z "$REPO" ]]; then
|
||||
print_error "Could not determine repository. Make sure you're in a GitHub repository."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_status "Working with repository: $REPO"
|
||||
|
||||
# Get all active workflows (both queued and in-progress)
|
||||
print_status "Fetching active workflows (queued and in-progress)..."
|
||||
QUEUED_WORKFLOWS=$(gh run list --status queued --json databaseId,displayTitle,headBranch,status --limit 100)
|
||||
IN_PROGRESS_WORKFLOWS=$(gh run list --status in_progress --json databaseId,displayTitle,headBranch,status --limit 100)
|
||||
|
||||
# Combine both lists
|
||||
ALL_WORKFLOWS=$(echo "$QUEUED_WORKFLOWS $IN_PROGRESS_WORKFLOWS" | jq -s 'add | unique_by(.databaseId)')
|
||||
|
||||
if [[ "$ALL_WORKFLOWS" == "[]" ]]; then
|
||||
print_status "No active workflows found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse and display active workflows
|
||||
echo
|
||||
print_warning "Found active workflows:"
|
||||
echo "$ALL_WORKFLOWS" | jq -r '.[] | " - \(.displayTitle) (Branch: \(.headBranch), Status: \(.status), ID: \(.databaseId))"'
|
||||
|
||||
echo
|
||||
read -p "Do you want to cancel ALL these workflows? (y/N): " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
print_status "Cancelled by user."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Cancel each workflow
|
||||
print_status "Cancelling workflows..."
|
||||
CANCELLED_COUNT=0
|
||||
FAILED_COUNT=0
|
||||
|
||||
while IFS= read -r WORKFLOW_ID; do
|
||||
if [[ -n "$WORKFLOW_ID" ]]; then
|
||||
print_status "Cancelling workflow ID: $WORKFLOW_ID"
|
||||
if gh run cancel "$WORKFLOW_ID" 2>/dev/null; then
|
||||
((CANCELLED_COUNT++))
|
||||
else
|
||||
print_error "Failed to cancel workflow ID: $WORKFLOW_ID"
|
||||
((FAILED_COUNT++))
|
||||
fi
|
||||
fi
|
||||
done < <(echo "$ALL_WORKFLOWS" | jq -r '.[].databaseId')
|
||||
|
||||
echo
|
||||
print_status "Summary:"
|
||||
echo " - Cancelled: $CANCELLED_COUNT workflows"
|
||||
if [[ $FAILED_COUNT -gt 0 ]]; then
|
||||
echo " - Failed: $FAILED_COUNT workflows"
|
||||
fi
|
||||
|
||||
print_status "Done!"
|
||||
|
||||
# Optional: Show remaining active workflows
|
||||
echo
|
||||
print_status "Checking for any remaining active workflows..."
|
||||
REMAINING_QUEUED=$(gh run list --status queued --json databaseId --limit 10)
|
||||
REMAINING_IN_PROGRESS=$(gh run list --status in_progress --json databaseId --limit 10)
|
||||
REMAINING_ALL=$(echo "$REMAINING_QUEUED $REMAINING_IN_PROGRESS" | jq -s 'add | unique_by(.databaseId)')
|
||||
|
||||
if [[ "$REMAINING_ALL" == "[]" ]]; then
|
||||
print_status "All workflows successfully cancelled."
|
||||
else
|
||||
REMAINING_COUNT=$(echo "$REMAINING_ALL" | jq '. | length')
|
||||
print_warning "Still $REMAINING_COUNT workflows active (may take a moment to update status)"
|
||||
fi
|
||||
@@ -87,6 +87,15 @@
|
||||
</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>
|
||||
<release version="2.7.12" date="2025-10-01">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.12</url>
|
||||
</release>
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.6.6
|
||||
2.6.7
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"]
|
||||
],
|
||||
"usb_product": "elecrow_eink",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "ELECROW-ThinkNode-M3",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "elecrow nrf",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_ELECROW_M4 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"]
|
||||
],
|
||||
"usb_product": "elecrow_thinknode_m4",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "ELECROW-ThinkNode-M4",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "ELECROW ThinkNode m4",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.elecrow.com/thinknode-m4-power-bank-lora-device-with-meshtastic-lora-tracker-function-powered-by-nrf52840.html",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_ELECROW_M6 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"]
|
||||
],
|
||||
"usb_product": "elecrow_thinknode_m6",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "ELECROW-ThinkNode-M6",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "ELECROW ThinkNode M6",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.elecrow.com/thinknode-m6-outdoor-solar-power-for-lora-powered-by-nrf52840-supports-gps.html",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-D BOARD_HAS_PSRAM",
|
||||
"-D ARDUINO_USB_CDC_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_MODE=0",
|
||||
"-D ARDUINO_RUNNING_CORE=1",
|
||||
"-D ARDUINO_EVENT_RUNNING_CORE=0"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"psram_type": "qio_opi",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "ELECROW-ThinkNode-M7"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "ELECROW ThinkNode M7",
|
||||
"upload": {
|
||||
"flash_size": "8MB",
|
||||
"maximum_ram_size": 524288,
|
||||
"maximum_size": 8388608,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.elecrow.com",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_MUZI_BASE -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [["0x239A", "0xcafe"]],
|
||||
"mcu": "nrf52840",
|
||||
"variant": "muzi-base",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Muzi Base",
|
||||
"url": "https://muzi.works/",
|
||||
"vendor": "MuziWorks",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"blackmagic",
|
||||
"cmsis-dap",
|
||||
"mbed",
|
||||
"stlink"
|
||||
],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
}
|
||||
}
|
||||
Vendored
+18
@@ -1,3 +1,21 @@
|
||||
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
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Sat, 11 Oct 2025 15:27:28 +0000
|
||||
|
||||
meshtasticd (2.7.12.0) unstable; urgency=medium
|
||||
|
||||
[ Austin Lane ]
|
||||
|
||||
Vendored
+1
@@ -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
|
||||
|
||||
+4
-4
@@ -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/6d8cc228298a1ecd9913aed757187e9527c1facc.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
|
||||
|
||||
+1
-1
Submodule protobufs updated: a1b8c3d171...52fa252f1e
@@ -154,7 +154,7 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
|
||||
if (!this->_enabled)
|
||||
return false;
|
||||
|
||||
if ((this->_server == NULL && this->_ip == INADDR_NONE) || this->_port == 0)
|
||||
if ((this->_server == NULL && this->_ip == IPAddress(0, 0, 0, 0)) || this->_port == 0)
|
||||
return false;
|
||||
|
||||
// Check priority against priMask values.
|
||||
|
||||
@@ -13,6 +13,11 @@ extern MemGet memGet;
|
||||
#define LED_STATE_ON 1
|
||||
#endif
|
||||
|
||||
// WIFI LED
|
||||
#ifndef WIFI_STATE_ON
|
||||
#define WIFI_STATE_ON 1
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// DEBUG
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -147,7 +152,9 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
|
||||
// Default Bluetooth PIN
|
||||
#define defaultBLEPin 123456
|
||||
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500)
|
||||
#if HAS_ETHERNET && defined(USE_CH390D)
|
||||
#include <ESP32_CH390.h>
|
||||
#elif HAS_ETHERNET && !defined(USE_WS5500)
|
||||
#include <RAK13800_W5100S.h>
|
||||
#endif // HAS_ETHERNET
|
||||
|
||||
|
||||
+188
-10
@@ -278,6 +278,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if defined(BATTERY_CHARGING_INV)
|
||||
// bit of trickery to show 99% up until the charge finishes
|
||||
if (!digitalRead(BATTERY_CHARGING_INV) && battery_SOC > 99)
|
||||
battery_SOC = 99;
|
||||
#endif
|
||||
return clamp((int)(battery_SOC), 0, 100);
|
||||
}
|
||||
|
||||
@@ -450,11 +455,13 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
// if it's not LOW - check the battery
|
||||
#else
|
||||
// if external powered that pin will be pulled up
|
||||
if (digitalRead(EXT_PWR_DETECT) == HIGH) {
|
||||
return true;
|
||||
}
|
||||
// if it's not HIGH - check the battery
|
||||
return digitalRead(EXT_PWR_DETECT) == HIGH;
|
||||
// don't check the battery.
|
||||
// very narrow backport from later 2.7 releases
|
||||
|
||||
#endif
|
||||
#elif defined(MUZI_BASE)
|
||||
return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
|
||||
#endif
|
||||
return getBattVoltage() > chargingVolt;
|
||||
}
|
||||
@@ -468,8 +475,12 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
|
||||
}
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
#if defined(ELECROW_ThinkNode_M6)
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value || isVbusIn();
|
||||
#elif EXT_CHRG_DETECT
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
#elif defined(BATTERY_CHARGING_INV)
|
||||
return !digitalRead(BATTERY_CHARGING_INV);
|
||||
#else
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
|
||||
if (hasINA()) {
|
||||
@@ -562,6 +573,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
config.power.device_battery_ina_address) {
|
||||
if (!ina226Sensor.isInitialized())
|
||||
return ina226Sensor.runOnce() > 0;
|
||||
return ina226Sensor.isRunning();
|
||||
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
|
||||
config.power.device_battery_ina_address) {
|
||||
if (!ina260Sensor.isInitialized())
|
||||
@@ -682,6 +694,8 @@ bool Power::setup()
|
||||
found = true;
|
||||
} else if (lipoChargerInit()) {
|
||||
found = true;
|
||||
} else if (serialBatteryInit()) {
|
||||
found = true;
|
||||
} else if (meshSolarInit()) {
|
||||
found = true;
|
||||
} else if (analogInit()) {
|
||||
@@ -691,7 +705,36 @@ bool Power::setup()
|
||||
#ifdef NRF_APM
|
||||
found = true;
|
||||
#endif
|
||||
|
||||
#ifdef EXT_PWR_DETECT
|
||||
attachInterrupt(
|
||||
EXT_PWR_DETECT,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
#ifdef BATTERY_CHARGING_INV
|
||||
attachInterrupt(
|
||||
BATTERY_CHARGING_INV,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
attachInterrupt(
|
||||
EXT_CHRG_DETECT,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
enabled = found;
|
||||
low_voltage_counter = 0;
|
||||
|
||||
@@ -828,8 +871,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()) {
|
||||
@@ -892,7 +938,7 @@ void Power::readPowerStatus()
|
||||
low_voltage_counter++;
|
||||
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
|
||||
if (low_voltage_counter > 10) {
|
||||
#ifdef ARCH_NRF52
|
||||
#if defined(ARCH_NRF52) && !defined(ELECROW_ThinkNode_M6)
|
||||
// We can't trigger deep sleep on NRF52, it's freezing the board
|
||||
LOG_DEBUG("Low voltage detected, but not trigger deep sleep");
|
||||
#else
|
||||
@@ -1538,4 +1584,136 @@ bool Power::meshSolarInit()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAS_SERIAL_BATTERY_LEVEL
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
/**
|
||||
* SerialBatteryLevel class for pulling battery information from a secondary MCU over serial.
|
||||
*/
|
||||
class SerialBatteryLevel : public HasBatteryLevel
|
||||
{
|
||||
|
||||
public:
|
||||
/**
|
||||
* Init the I2C meshSolar battery level sensor
|
||||
*/
|
||||
bool runOnce()
|
||||
{
|
||||
BatterySerial.begin(4800);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||
*/
|
||||
virtual int getBatteryPercent() override { return v_percent; }
|
||||
|
||||
/**
|
||||
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||||
*/
|
||||
virtual uint16_t getBattVoltage() override { return voltage * 1000; }
|
||||
|
||||
/**
|
||||
* return true if there is a battery installed in this unit
|
||||
*/
|
||||
virtual bool isBatteryConnect() override
|
||||
{
|
||||
// definitely need to gobble up more bytes at once
|
||||
if (BatterySerial.available() > 5) {
|
||||
// LOG_WARN("SerialBatteryLevel: %u bytes available", BatterySerial.available());
|
||||
while (BatterySerial.available() > 11) {
|
||||
BatterySerial.read(); // flush old data
|
||||
}
|
||||
// LOG_WARN("SerialBatteryLevel: %u bytes now available", BatterySerial.available());
|
||||
int tries = 0;
|
||||
while (BatterySerial.read() != 0xFE) {
|
||||
tries++; // wait for start byte
|
||||
if (tries > 10) {
|
||||
LOG_WARN("SerialBatteryLevel: no start byte found");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Data[1] = BatterySerial.read();
|
||||
Data[2] = BatterySerial.read();
|
||||
Data[3] = BatterySerial.read();
|
||||
Data[4] = BatterySerial.read();
|
||||
Data[5] = BatterySerial.read();
|
||||
if (Data[5] != 0xFD) {
|
||||
LOG_WARN("SerialBatteryLevel: invalid end byte %02x", Data[5]);
|
||||
return true;
|
||||
}
|
||||
v_percent = Data[1];
|
||||
voltage = Data[2] + (((float)Data[3]) / 100) + (((float)Data[4]) / 10000);
|
||||
voltage *= 2;
|
||||
// LOG_WARN("SerialBatteryLevel: received data %u, %f, %02x", v_percent, voltage, Data[5]);
|
||||
return true;
|
||||
}
|
||||
// This function runs first, so use it to grab the latest data from the secondary MCU
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if there is an external power source detected
|
||||
*/
|
||||
virtual bool isVbusIn() override
|
||||
{
|
||||
#if defined(EXT_CHRG_DETECT)
|
||||
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool isCharging() override
|
||||
{
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
|
||||
#endif
|
||||
// by default, we check the battery voltage only
|
||||
return isVbusIn();
|
||||
}
|
||||
|
||||
private:
|
||||
SoftwareSerial BatterySerial = SoftwareSerial(SERIAL_BATTERY_RX, SERIAL_BATTERY_TX);
|
||||
uint8_t Data[6] = {0};
|
||||
int v_percent = 0;
|
||||
float voltage = 0.0;
|
||||
};
|
||||
|
||||
SerialBatteryLevel serialBatteryLevel;
|
||||
|
||||
/**
|
||||
* Init the serial battery level sensor
|
||||
*/
|
||||
bool Power::serialBatteryInit()
|
||||
{
|
||||
#ifdef EXT_PWR_DETECT
|
||||
pinMode(EXT_PWR_DETECT, INPUT);
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
|
||||
#endif
|
||||
|
||||
bool result = serialBatteryLevel.runOnce();
|
||||
LOG_DEBUG("Power::serialBatteryInit serial battery sensor is %s", result ? "ready" : "not ready yet");
|
||||
if (!result)
|
||||
return false;
|
||||
batteryLevel = &serialBatteryLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
/**
|
||||
* If this device has no serial battery level sensor, don't try to use it.
|
||||
*/
|
||||
bool Power::serialBatteryInit()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
|
||||
+79
-3
@@ -6,6 +6,11 @@
|
||||
#include "Tone.h"
|
||||
#endif
|
||||
|
||||
#if defined(HAS_I2S)
|
||||
#include "main.h"
|
||||
#include <unordered_map>
|
||||
#endif
|
||||
|
||||
#if !defined(ARCH_PORTDUINO)
|
||||
extern "C" void delay(uint32_t dwMs);
|
||||
#endif
|
||||
@@ -16,6 +21,7 @@ struct ToneDuration {
|
||||
};
|
||||
|
||||
// Some common frequencies.
|
||||
#define NOTE_SILENT 1
|
||||
#define NOTE_C3 131
|
||||
#define NOTE_CS3 139
|
||||
#define NOTE_D3 147
|
||||
@@ -29,13 +35,70 @@ struct ToneDuration {
|
||||
#define NOTE_AS3 233
|
||||
#define NOTE_B3 247
|
||||
#define NOTE_CS4 277
|
||||
#define NOTE_B4 494
|
||||
#define NOTE_F5 698
|
||||
#define NOTE_G6 1568
|
||||
#define NOTE_E7 2637
|
||||
|
||||
#define NOTE_C4 262
|
||||
#define NOTE_E4 330
|
||||
#define NOTE_G4 392
|
||||
#define NOTE_A4 440
|
||||
#define NOTE_C5 523
|
||||
#define NOTE_E5 659
|
||||
#define NOTE_G5 784
|
||||
|
||||
const int DURATION_1_16 = 62; // 1/16 note
|
||||
const int DURATION_1_8 = 125; // 1/8 note
|
||||
const int DURATION_1_4 = 250; // 1/4 note
|
||||
const int DURATION_1_2 = 500; // 1/2 note
|
||||
const int DURATION_3_4 = 750; // 1/4 note
|
||||
const int DURATION_1_1 = 1000; // 1/1 note
|
||||
|
||||
#ifdef HAS_I2S
|
||||
void playTonesRTTTL(const ToneDuration *tone_durations, int size)
|
||||
{
|
||||
// translate ToneDuration[] to RTTTL string and play using audioThread
|
||||
static std::unordered_map<int, std::string> freqToNote = {
|
||||
{NOTE_C3, "c4"}, {NOTE_CS3, "c#4"}, {NOTE_D3, "d4"}, {NOTE_DS3, "d#4"}, {NOTE_E3, "e4"}, {NOTE_F3, "f4"},
|
||||
{NOTE_FS3, "f#4"}, {NOTE_G3, "g4"}, {NOTE_GS3, "g#4"}, {NOTE_A3, "a4"}, {NOTE_AS3, "a#4"}, {NOTE_B3, "b4"},
|
||||
{NOTE_C4, "c5"}, {NOTE_E4, "e5"}, {NOTE_G4, "g5"}, {NOTE_A4, "a5"}, {NOTE_C5, "c6"}, {NOTE_E5, "e6"},
|
||||
{NOTE_G5, "g6"}, {NOTE_F5, "f6"}, {NOTE_G6, "g7"}, {NOTE_E7, "e8"}};
|
||||
|
||||
char rtttl[128] = "tone:d=32,o=4,b=200:"; // default duration and octave
|
||||
for (int i = 0; i < size; i++) {
|
||||
const auto &td = tone_durations[i];
|
||||
std::string note = "b4";
|
||||
if (freqToNote.find(td.frequency_khz) != freqToNote.end()) {
|
||||
note = freqToNote[td.frequency_khz];
|
||||
}
|
||||
int dur = 32; // default duration
|
||||
if (td.duration_ms >= 1000)
|
||||
dur = 1;
|
||||
else if (td.duration_ms >= 500)
|
||||
dur = 2;
|
||||
else if (td.duration_ms >= 250)
|
||||
dur = 4;
|
||||
else if (td.duration_ms >= 125)
|
||||
dur = 8;
|
||||
else if (td.duration_ms >= 62)
|
||||
dur = 16;
|
||||
else
|
||||
dur = 32;
|
||||
|
||||
char noteStr[64];
|
||||
snprintf(noteStr, sizeof(noteStr), "%s,%d", note.c_str(), dur);
|
||||
strncat(rtttl, noteStr, sizeof(rtttl) - strlen(rtttl) - 1);
|
||||
|
||||
audioThread->beginRttl(rtttl, strlen(rtttl));
|
||||
while (audioThread->isPlaying()) {
|
||||
delay(10);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void playTones(const ToneDuration *tone_durations, int size)
|
||||
{
|
||||
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
|
||||
@@ -43,7 +106,13 @@ void playTones(const ToneDuration *tone_durations, int size)
|
||||
// Buzzer is disabled or not set to system tones
|
||||
return;
|
||||
}
|
||||
#ifdef PIN_BUZZER
|
||||
#ifdef HAS_I2S
|
||||
if (moduleConfig.external_notification.use_i2s_as_buzzer && audioThread) {
|
||||
playTonesRTTTL(tone_durations, size);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if defined(PIN_BUZZER)
|
||||
if (!config.device.buzzer_gpio)
|
||||
config.device.buzzer_gpio = PIN_BUZZER;
|
||||
#endif
|
||||
@@ -59,7 +128,7 @@ void playTones(const ToneDuration *tone_durations, int size)
|
||||
|
||||
void playBeep()
|
||||
{
|
||||
ToneDuration melody[] = {{NOTE_B3, DURATION_1_8}};
|
||||
ToneDuration melody[] = {{NOTE_B3, DURATION_1_16}};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
@@ -96,7 +165,14 @@ void playShutdownMelody()
|
||||
void playChirp()
|
||||
{
|
||||
// A short, friendly "chirp" sound for key presses
|
||||
ToneDuration melody[] = {{NOTE_AS3, 20}}; // Very short AS3 note
|
||||
ToneDuration melody[] = {{NOTE_AS3, 20}}; // Short AS3 note
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
void playClick()
|
||||
{
|
||||
// A very short "click" sound with minimum delay; ideal for rotary encoder events
|
||||
ToneDuration melody[] = {{NOTE_AS3, 1}}; // Very Short AS3
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
|
||||
+14
-3
@@ -126,9 +126,9 @@ 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 STATION_G2
|
||||
#define NUM_PA_POINTS 19
|
||||
#define TX_GAIN_LORA 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 19, 19, 18, 18
|
||||
#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
|
||||
@@ -228,6 +228,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define ICM20948_ADDR_ALT 0x68
|
||||
#define BHI260AP_ADDR 0x28
|
||||
#define BMM150_ADDR 0x13
|
||||
#define DA217_ADDR 0x26
|
||||
#define BMI270_ADDR 0x68
|
||||
#define BMI270_ADDR_ALT 0x69
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// LED
|
||||
@@ -250,6 +253,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)
|
||||
@@ -394,6 +398,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define HAS_RGB_LED
|
||||
#endif
|
||||
|
||||
#ifndef LED_STATE_OFF
|
||||
#define LED_STATE_OFF 0
|
||||
#endif
|
||||
#ifndef LED_STATE_ON
|
||||
#define LED_STATE_ON 1
|
||||
#endif
|
||||
|
||||
// default mapping of pins
|
||||
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
|
||||
#define ALT_BUTTON_PIN PIN_BUTTON2
|
||||
|
||||
@@ -37,8 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150};
|
||||
return firstOfOrNONE(9, types);
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150, BMI270};
|
||||
return firstOfOrNONE(10, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAQI() const
|
||||
|
||||
@@ -82,7 +82,12 @@ class ScanI2C
|
||||
BHI260AP,
|
||||
BMM150,
|
||||
TSL2561,
|
||||
DRV2605
|
||||
DRV2605,
|
||||
BH1750,
|
||||
DA217,
|
||||
CHSC6X,
|
||||
CST226SE,
|
||||
BMI270
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "ScanI2CConsumer.h"
|
||||
#include <forward_list>
|
||||
|
||||
static std::forward_list<ScanI2CConsumer *> ScanI2CConsumers;
|
||||
|
||||
ScanI2CConsumer::ScanI2CConsumer()
|
||||
{
|
||||
ScanI2CConsumers.push_front(this);
|
||||
}
|
||||
|
||||
void ScanI2CCompleted(ScanI2C *i2cScanner)
|
||||
{
|
||||
for (ScanI2CConsumer *consumer : ScanI2CConsumers) {
|
||||
consumer->i2cScanFinished(i2cScanner);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "ScanI2C.h"
|
||||
#include <stddef.h>
|
||||
|
||||
class ScanI2CConsumer
|
||||
{
|
||||
public:
|
||||
ScanI2CConsumer();
|
||||
virtual void i2cScanFinished(ScanI2C *i2cScanner) = 0;
|
||||
};
|
||||
|
||||
void ScanI2CCompleted(ScanI2C *i2cScanner);
|
||||
@@ -378,7 +378,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
|
||||
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
|
||||
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c || registerValue == 0xc8d) {
|
||||
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c ||
|
||||
registerValue == 0xc8d) {
|
||||
type = SHT4X;
|
||||
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
||||
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
|
||||
@@ -484,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);
|
||||
@@ -509,14 +529,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
}
|
||||
break;
|
||||
|
||||
case ICM20948_ADDR: // same as BMX160_ADDR
|
||||
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
|
||||
case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT
|
||||
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
|
||||
#ifdef HAS_ICM20948
|
||||
type = ICM20948;
|
||||
logFoundDevice("ICM20948", (uint8_t)addr.address);
|
||||
break;
|
||||
#endif
|
||||
if (registerValue == 0xEA) {
|
||||
type = ICM20948;
|
||||
logFoundDevice("ICM20948", (uint8_t)addr.address);
|
||||
break;
|
||||
} else if (addr.address == BMX160_ADDR) {
|
||||
} else if (registerValue == 0x24) {
|
||||
type = BMI270;
|
||||
logFoundDevice("BMI270", (uint8_t)addr.address);
|
||||
break;
|
||||
} else if (registerValue == 0xD8) { // BMX160 chip ID at register 0x00
|
||||
type = BMX160;
|
||||
logFoundDevice("BMX160", (uint8_t)addr.address);
|
||||
break;
|
||||
@@ -580,7 +609,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
|
||||
scanPort(port, nullptr, 0);
|
||||
}
|
||||
|
||||
TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const
|
||||
TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address)
|
||||
{
|
||||
if (address.port == ScanI2C::I2CPort::WIRE) {
|
||||
return &Wire;
|
||||
|
||||
@@ -23,12 +23,12 @@ class ScanI2CTwoWire : public ScanI2C
|
||||
|
||||
ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override;
|
||||
|
||||
TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const;
|
||||
|
||||
bool exists(ScanI2C::DeviceType) const override;
|
||||
|
||||
size_t countDevices() const override;
|
||||
|
||||
static TwoWire *fetchI2CBus(ScanI2C::DeviceAddress);
|
||||
|
||||
protected:
|
||||
FoundDevice firstOfOrNONE(size_t, DeviceType[]) const override;
|
||||
|
||||
|
||||
+11
-21
@@ -494,17 +494,6 @@ bool GPS::setup()
|
||||
if (!didSerialInit) {
|
||||
int msglen = 0;
|
||||
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
#ifdef TRACKER_T1000_E
|
||||
// add power up/down strategy, improve ag3335 detection success
|
||||
digitalWrite(PIN_GPS_EN, LOW);
|
||||
delay(500);
|
||||
digitalWrite(GPS_VRTC_EN, LOW);
|
||||
delay(1000);
|
||||
digitalWrite(GPS_VRTC_EN, HIGH);
|
||||
delay(500);
|
||||
digitalWrite(PIN_GPS_EN, HIGH);
|
||||
delay(1000);
|
||||
#endif
|
||||
if (probeTries < GPS_PROBETRIES) {
|
||||
gnssModel = probe(serialSpeeds[speedSelect]);
|
||||
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
@@ -905,14 +894,11 @@ void GPS::writePinEN(bool on)
|
||||
void GPS::writePinStandby(bool standby)
|
||||
{
|
||||
#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones
|
||||
|
||||
// Determine the new value for the pin
|
||||
// Normally: active HIGH for awake
|
||||
#ifdef PIN_GPS_STANDBY_INVERTED
|
||||
bool val = standby;
|
||||
#else
|
||||
bool val = !standby;
|
||||
#endif
|
||||
bool val;
|
||||
if (standby)
|
||||
val = GPS_STANDBY_ACTIVE;
|
||||
else
|
||||
val = !GPS_STANDBY_ACTIVE;
|
||||
|
||||
// Write and log
|
||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||
@@ -1670,8 +1656,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
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
#define GPS_EN_ACTIVE 1
|
||||
#endif
|
||||
|
||||
// Allow defining the polarity of the STANDBY output. default is LOW for standby
|
||||
#ifndef GPS_STANDBY_ACTIVE
|
||||
#define GPS_STANDBY_ACTIVE LOW
|
||||
#endif
|
||||
|
||||
static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL;
|
||||
static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000;
|
||||
|
||||
|
||||
@@ -112,7 +112,11 @@ RTCSetResult readFromRTC()
|
||||
#elif defined(RX8130CE_RTC)
|
||||
if (rtc_found.address == RX8130CE_RTC) {
|
||||
uint32_t now = millis();
|
||||
#ifdef MUZI_BASE
|
||||
ArtronShop_RX8130CE rtc(&Wire1);
|
||||
#else
|
||||
ArtronShop_RX8130CE rtc(&Wire);
|
||||
#endif
|
||||
tm t;
|
||||
if (rtc.getTime(&t)) {
|
||||
tv.tv_sec = gm_mktime(&t);
|
||||
@@ -245,7 +249,11 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
|
||||
}
|
||||
#elif defined(RX8130CE_RTC)
|
||||
if (rtc_found.address == RX8130CE_RTC) {
|
||||
#ifdef MUZI_BASE
|
||||
ArtronShop_RX8130CE rtc(&Wire1);
|
||||
#else
|
||||
ArtronShop_RX8130CE rtc(&Wire);
|
||||
#endif
|
||||
tm *t = gmtime(&tv->tv_sec);
|
||||
if (rtc.setTime(*t)) {
|
||||
LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1,
|
||||
|
||||
+31
-4
@@ -435,6 +435,17 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
PMU->enablePowerOutput(XPOWERS_ALDO2);
|
||||
#endif
|
||||
|
||||
// some screens seem to need a kick in the pants to turn back on
|
||||
#if defined(MUZI_BASE) || defined(M5STACK_CARDPUTER_ADV)
|
||||
dispdev->init();
|
||||
dispdev->setBrightness(brightness);
|
||||
dispdev->flipScreenVertically();
|
||||
dispdev->resetDisplay();
|
||||
#ifdef SCREEN_12V_ENABLE
|
||||
digitalWrite(SCREEN_12V_ENABLE, HIGH);
|
||||
#endif
|
||||
delay(100);
|
||||
#endif
|
||||
#if !ARCH_PORTDUINO
|
||||
dispdev->displayOn();
|
||||
#endif
|
||||
@@ -443,7 +454,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
|
||||
|
||||
@@ -456,9 +467,11 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
#if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2)
|
||||
ui->init();
|
||||
#endif
|
||||
#ifdef USE_ST7789
|
||||
#if defined(USE_ST7789) && defined(VTFT_LEDA)
|
||||
#ifdef VTFT_CTRL
|
||||
pinMode(VTFT_CTRL, OUTPUT);
|
||||
digitalWrite(VTFT_CTRL, LOW);
|
||||
#endif
|
||||
ui->init();
|
||||
#ifdef ESP_PLATFORM
|
||||
analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT);
|
||||
@@ -484,11 +497,19 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
#endif
|
||||
|
||||
dispdev->displayOff();
|
||||
|
||||
#ifdef SCREEN_12V_ENABLE
|
||||
digitalWrite(SCREEN_12V_ENABLE, LOW);
|
||||
#endif
|
||||
#ifdef USE_ST7789
|
||||
SPI1.end();
|
||||
#if defined(ARCH_ESP32)
|
||||
#ifdef VTFT_LEDA
|
||||
pinMode(VTFT_LEDA, ANALOG);
|
||||
#endif
|
||||
#ifdef VTFT_CTRL
|
||||
pinMode(VTFT_CTRL, ANALOG);
|
||||
#endif
|
||||
pinMode(ST7789_RESET, ANALOG);
|
||||
pinMode(ST7789_RS, ANALOG);
|
||||
pinMode(ST7789_NSS, ANALOG);
|
||||
@@ -534,7 +555,7 @@ void Screen::setup()
|
||||
static_cast<AutoOLEDWire *>(dispdev)->setDetected(model);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SH1107_128_64
|
||||
#if defined(USE_SH1107_128_64) || defined(USE_SH1107)
|
||||
static_cast<SH1106Wire *>(dispdev)->setSubtype(7);
|
||||
#endif
|
||||
|
||||
@@ -542,6 +563,9 @@ void Screen::setup()
|
||||
// Apply custom RGB color (e.g. Heltec T114/T190)
|
||||
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
|
||||
#endif
|
||||
#if defined(MUZI_BASE)
|
||||
dispdev->delayPoweron = true;
|
||||
#endif
|
||||
|
||||
// === Initialize display and UI system ===
|
||||
ui->init();
|
||||
@@ -1428,6 +1452,9 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
}
|
||||
nodeDB->updateGUI = false;
|
||||
break;
|
||||
case STATUS_TYPE_POWER:
|
||||
forceDisplay(true);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1487,7 +1514,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
||||
strcpy(banner, "Alert Received");
|
||||
}
|
||||
screen->showSimpleBanner(banner, 3000);
|
||||
} else if (!channel.settings.mute) {
|
||||
} else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
|
||||
if (longName && longName[0]) {
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
strcpy(banner, "New Message");
|
||||
|
||||
@@ -249,6 +249,8 @@ class Screen : public concurrency::OSThread
|
||||
|
||||
bool isOverlayBannerShowing();
|
||||
|
||||
bool isScreenOn() { return screenOn; }
|
||||
|
||||
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
|
||||
// FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class
|
||||
char ourId[5];
|
||||
@@ -726,7 +728,11 @@ class Screen : public concurrency::OSThread
|
||||
DebugInfo debugInfo;
|
||||
|
||||
/// Display device
|
||||
#ifdef USE_ST7789
|
||||
ST7789Spi *dispdev;
|
||||
#else
|
||||
OLEDDisplay *dispdev;
|
||||
#endif
|
||||
|
||||
/// UI helper for rendering to frames and switching between them
|
||||
OLEDDisplayUi *ui;
|
||||
|
||||
@@ -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 {
|
||||
@@ -762,19 +762,52 @@ void menuHandler::nodeListMenu()
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::nodeNameLengthMenu()
|
||||
{
|
||||
enum OptionsNumbers { Back, Long, Short };
|
||||
static const char *optionsArray[] = {"Back", "Long", "Short"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Node Name Length";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 3;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == Long) {
|
||||
// Set names to long
|
||||
LOG_INFO("Setting names to long");
|
||||
config.display.use_long_node_name = true;
|
||||
} else if (selected == Short) {
|
||||
// Set names to short
|
||||
LOG_INFO("Setting names to short");
|
||||
config.display.use_long_node_name = false;
|
||||
} else if (selected == Back) {
|
||||
menuQueue = screen_options_menu;
|
||||
screen->runNow();
|
||||
}
|
||||
};
|
||||
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);
|
||||
@@ -910,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);
|
||||
}
|
||||
@@ -1191,9 +1226,9 @@ void menuHandler::traceRouteMenu()
|
||||
void menuHandler::testMenu()
|
||||
{
|
||||
|
||||
enum optionsNumbers { Back, NumberPicker, ShowChirpy };
|
||||
static const char *optionsArray[4] = {"Back"};
|
||||
static int optionsEnumArray[4] = {Back};
|
||||
enum optionsNumbers { Back, NumberPicker, ShowChirpy, TestAnnounce };
|
||||
static const char *optionsArray[5] = {"Back"};
|
||||
static int optionsEnumArray[5] = {Back};
|
||||
int options = 1;
|
||||
|
||||
optionsArray[options] = "Number Picker";
|
||||
@@ -1201,6 +1236,10 @@ void menuHandler::testMenu()
|
||||
|
||||
optionsArray[options] = screen->isFrameHidden("chirpy") ? "Show Chirpy" : "Hide Chirpy";
|
||||
optionsEnumArray[options++] = ShowChirpy;
|
||||
#ifdef HAS_I2S
|
||||
optionsArray[options] = "Test Announce";
|
||||
optionsEnumArray[options++] = TestAnnounce;
|
||||
#endif
|
||||
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Hidden Test Menu";
|
||||
@@ -1215,6 +1254,10 @@ void menuHandler::testMenu()
|
||||
screen->toggleFrameVisibility("chirpy");
|
||||
screen->setFrames(Screen::FOCUS_SYSTEM);
|
||||
|
||||
} else if (selected == TestAnnounce) {
|
||||
#ifdef HAS_I2S
|
||||
audioThread->readAloud("This is a test of the emergency broadcast system. This is only a test.");
|
||||
#endif
|
||||
} else {
|
||||
menuQueue = system_base_menu;
|
||||
screen->runNow();
|
||||
@@ -1304,11 +1347,16 @@ void menuHandler::screenOptionsMenu()
|
||||
hasSupportBrightness = false;
|
||||
#endif
|
||||
|
||||
enum optionsNumbers { Back, Brightness, ScreenColor };
|
||||
static const char *optionsArray[4] = {"Back"};
|
||||
static int optionsEnumArray[4] = {Back};
|
||||
enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor };
|
||||
static const char *optionsArray[5] = {"Back"};
|
||||
static int optionsEnumArray[5] = {Back};
|
||||
int options = 1;
|
||||
|
||||
#if defined(T_DECK) || defined(T_LORA_PAGER)
|
||||
optionsArray[options] = "Show Long/Short Name";
|
||||
optionsEnumArray[options++] = NodeNameLength;
|
||||
#endif
|
||||
|
||||
// Only show brightness for B&W displays
|
||||
if (hasSupportBrightness) {
|
||||
optionsArray[options] = "Brightness";
|
||||
@@ -1333,6 +1381,9 @@ void menuHandler::screenOptionsMenu()
|
||||
} else if (selected == ScreenColor) {
|
||||
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
|
||||
screen->runNow();
|
||||
} else if (selected == NodeNameLength) {
|
||||
menuHandler::menuQueue = menuHandler::node_name_length_menu;
|
||||
screen->runNow();
|
||||
} else {
|
||||
menuQueue = system_base_menu;
|
||||
screen->runNow();
|
||||
@@ -1431,6 +1482,8 @@ void menuHandler::FrameToggles_menu()
|
||||
lora,
|
||||
clock,
|
||||
show_favorites,
|
||||
show_telemetry,
|
||||
show_power,
|
||||
enumEnd
|
||||
};
|
||||
static const char *optionsArray[enumEnd] = {"Finish"};
|
||||
@@ -1469,6 +1522,12 @@ void menuHandler::FrameToggles_menu()
|
||||
optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
|
||||
optionsEnumArray[options++] = show_favorites;
|
||||
|
||||
optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Telemetry" : "Show Telemetry";
|
||||
optionsEnumArray[options++] = show_telemetry;
|
||||
|
||||
optionsArray[options] = moduleConfig.telemetry.power_screen_enabled ? "Hide Power" : "Show Power";
|
||||
optionsEnumArray[options++] = show_power;
|
||||
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Show/Hide Frames";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
@@ -1523,6 +1582,14 @@ void menuHandler::FrameToggles_menu()
|
||||
screen->toggleFrameVisibility("show_favorites");
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
} else if (selected == show_telemetry) {
|
||||
moduleConfig.telemetry.environment_screen_enabled = !moduleConfig.telemetry.environment_screen_enabled;
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
} else if (selected == show_power) {
|
||||
moduleConfig.telemetry.power_screen_enabled = !moduleConfig.telemetry.power_screen_enabled;
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
@@ -1594,6 +1661,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case brightness_picker:
|
||||
BrightnessPickerMenu();
|
||||
break;
|
||||
case node_name_length_menu:
|
||||
nodeNameLengthMenu();
|
||||
break;
|
||||
case reboot_menu:
|
||||
rebootMenu();
|
||||
break;
|
||||
|
||||
@@ -43,6 +43,7 @@ class menuHandler
|
||||
key_verification_final_prompt,
|
||||
trace_route_menu,
|
||||
throttle_message,
|
||||
node_name_length_menu,
|
||||
FrameToggles
|
||||
};
|
||||
static screenMenus menuQueue;
|
||||
@@ -85,6 +86,7 @@ class menuHandler
|
||||
static void notificationsMenu();
|
||||
static void screenOptionsMenu();
|
||||
static void powerMenu();
|
||||
static void nodeNameLengthMenu();
|
||||
static void FrameToggles_menu();
|
||||
static void textMessageMenu();
|
||||
|
||||
|
||||
@@ -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,28 +53,56 @@ 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] = "?";
|
||||
if (node->has_user && strlen(node->user.short_name) > 0) {
|
||||
bool valid = true;
|
||||
const char *name = node->user.short_name;
|
||||
for (size_t i = 0; i < strlen(name); i++) {
|
||||
uint8_t c = (uint8_t)name[i];
|
||||
if (c < 32 || c > 126) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
strncpy(nodeName, name, sizeof(nodeName) - 1);
|
||||
nodeName[sizeof(nodeName) - 1] = '\0';
|
||||
if (config.display.use_long_node_name == true) {
|
||||
if (node->has_user && strlen(node->user.long_name) > 0) {
|
||||
name = node->user.long_name;
|
||||
} else {
|
||||
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
|
||||
}
|
||||
} else {
|
||||
if (node->has_user && strlen(node->user.short_name) > 0) {
|
||||
name = node->user.short_name;
|
||||
} else {
|
||||
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
|
||||
}
|
||||
}
|
||||
|
||||
// Use sanitizeString() function and copy directly into nodeName
|
||||
std::string sanitized_name = sanitizeString(name ? name : "");
|
||||
|
||||
if (!sanitized_name.empty()) {
|
||||
strncpy(nodeName, sanitized_name.c_str(), sizeof(nodeName) - 1);
|
||||
nodeName[sizeof(nodeName) - 1] = '\0';
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -141,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);
|
||||
@@ -186,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);
|
||||
@@ -230,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());
|
||||
@@ -325,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);
|
||||
@@ -356,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
|
||||
@@ -464,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);
|
||||
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
#if defined(M5STACK_CARDPUTER_ADV)
|
||||
|
||||
#include "CardputerKeyboard.h"
|
||||
#include "main.h"
|
||||
|
||||
#define _TCA8418_COLS 8
|
||||
#define _TCA8418_ROWS 7
|
||||
#define _TCA8418_NUM_KEYS 56
|
||||
|
||||
#define _TCA8418_MULTI_TAP_THRESHOLD 1500
|
||||
|
||||
using Key = TCA8418KeyboardBase::TCA8418Key;
|
||||
|
||||
constexpr uint8_t modifierFnKey = 2;
|
||||
constexpr uint8_t modifierFn = 0b0010;
|
||||
constexpr uint8_t modifierCtrlKey = 3;
|
||||
constexpr uint8_t modifierShiftKey = 6;
|
||||
constexpr uint8_t modifierShift = 0b0001;
|
||||
constexpr uint8_t modifierOptKey = 7;
|
||||
constexpr uint8_t modifierAltKey = 11;
|
||||
|
||||
// Num chars per key, Modulus for rotating through characters
|
||||
static uint8_t CardputerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
|
||||
|
||||
static unsigned char CardputerTapMap[_TCA8418_NUM_KEYS][3] = {{'`', '~', Key::ESC},
|
||||
{Key::TAB, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},
|
||||
{'1', '!', 0x00},
|
||||
{'q', 'Q', Key::REBOOT},
|
||||
{0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},
|
||||
{'2', '@', 0x00},
|
||||
{'w', 'W', 0x00},
|
||||
{'a', 'A', 0x00},
|
||||
{0x00, 0x00, 0x00},
|
||||
{'3', '#', 0x00},
|
||||
{'e', 'E', 0x00},
|
||||
{'s', 'S', 0x00},
|
||||
{'z', 'Z', 0x00},
|
||||
{'4', '$', 0x00},
|
||||
{'r', 'R', 0x00},
|
||||
{'d', 'D', 0x00},
|
||||
{'x', 'X', 0x00},
|
||||
{'5', '%', 0x00},
|
||||
{'t', 'T', 0x00},
|
||||
{'f', 'F', 0x00},
|
||||
{'c', 'C', 0x00},
|
||||
{'6', '^', 0x00},
|
||||
{'y', 'Y', 0x00},
|
||||
{'g', 'G', Key::GPS_TOGGLE},
|
||||
{'v', 'V', 0x00},
|
||||
{'7', '&', 0x00},
|
||||
{'u', 'U', 0x00},
|
||||
{'h', 'H', 0x00},
|
||||
{'b', 'B', Key::BT_TOGGLE},
|
||||
{'8', '*', 0x00},
|
||||
{'i', 'I', 0x00},
|
||||
{'j', 'J', 0x00},
|
||||
{'n', 'N', 0x00},
|
||||
{'9', '(', 0x00},
|
||||
{'o', 'O', 0x00},
|
||||
{'k', 'K', 0x00},
|
||||
{'m', 'M', Key::MUTE_TOGGLE},
|
||||
{'0', ')', 0x00},
|
||||
{'p', 'P', Key::SEND_PING},
|
||||
{'l', 'L', 0x00},
|
||||
{',', '<', Key::LEFT},
|
||||
{'_', '-', 0x00},
|
||||
{'[', '{', 0x00},
|
||||
{';', ':', Key::UP},
|
||||
{'.', '>', Key::DOWN},
|
||||
{'=', '+', 0x00},
|
||||
{']', '}', 0x00},
|
||||
{'\'', '"', 0x00},
|
||||
{'/', '?', Key::RIGHT},
|
||||
{Key::BSP, 0x00, 0x00},
|
||||
{'\\', '|', 0x00},
|
||||
{Key::SELECT, 0x00, 0x00},
|
||||
{' ', ' ', ' '}};
|
||||
|
||||
CardputerKeyboard::CardputerKeyboard()
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
|
||||
last_tap(0L), char_idx(0), tap_interval(0)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void CardputerKeyboard::reset(void)
|
||||
{
|
||||
TCA8418KeyboardBase::reset();
|
||||
}
|
||||
|
||||
// handle multi-key presses (shift and alt)
|
||||
void CardputerKeyboard::trigger()
|
||||
{
|
||||
uint8_t count = keyCount();
|
||||
if (count == 0)
|
||||
return;
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
|
||||
uint8_t key = k & 0x7F;
|
||||
if (k & 0x80) {
|
||||
pressed(key);
|
||||
} else {
|
||||
released();
|
||||
state = Idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardputerKeyboard::pressed(uint8_t key)
|
||||
{
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
return; // Invalid key
|
||||
}
|
||||
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
state = Held;
|
||||
|
||||
uint32_t now = millis();
|
||||
tap_interval = now - last_tap;
|
||||
|
||||
updateModifierFlag(next_key);
|
||||
if (isModifierKey(next_key)) {
|
||||
last_modifier_time = now;
|
||||
}
|
||||
|
||||
if (tap_interval < 0) {
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0;
|
||||
} else {
|
||||
char_idx += 1;
|
||||
}
|
||||
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
}
|
||||
|
||||
void CardputerKeyboard::released()
|
||||
{
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
last_tap = now;
|
||||
|
||||
if (inputBroker->menuMode && modifierFlag == 0) {
|
||||
if (last_key == 0 || last_key == 43 || last_key == 46 || last_key == 47 ||
|
||||
last_key == 51) { // esc, left, up, down, right key
|
||||
modifierFlag = modifierFn;
|
||||
}
|
||||
}
|
||||
|
||||
queueEvent(CardputerTapMap[last_key][modifierFlag % CardputerTapMod[last_key]]);
|
||||
if (isModifierKey(last_key) == false)
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
void CardputerKeyboard::updateModifierFlag(uint8_t key)
|
||||
{
|
||||
if (key == modifierShiftKey) {
|
||||
modifierFlag ^= modifierShift;
|
||||
} else if (key == modifierFnKey) {
|
||||
modifierFlag ^= modifierFn;
|
||||
}
|
||||
}
|
||||
|
||||
bool CardputerKeyboard::isModifierKey(uint8_t key)
|
||||
{
|
||||
return (key == modifierShiftKey || key == modifierFnKey);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "TCA8418KeyboardBase.h"
|
||||
|
||||
class CardputerKeyboard : public TCA8418KeyboardBase
|
||||
{
|
||||
public:
|
||||
CardputerKeyboard();
|
||||
void reset(void);
|
||||
void trigger(void) override;
|
||||
virtual ~CardputerKeyboard() {}
|
||||
|
||||
protected:
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
|
||||
void updateModifierFlag(uint8_t key);
|
||||
bool isModifierKey(uint8_t key);
|
||||
|
||||
private:
|
||||
uint8_t modifierFlag;
|
||||
uint32_t last_modifier_time;
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -18,6 +18,7 @@ enum input_broker_event {
|
||||
INPUT_BROKER_USER_PRESS,
|
||||
INPUT_BROKER_ALT_PRESS,
|
||||
INPUT_BROKER_ALT_LONG,
|
||||
INPUT_BROKER_FACTORY_RST = 0x9a,
|
||||
INPUT_BROKER_SHUTDOWN = 0x9b,
|
||||
INPUT_BROKER_GPS_TOGGLE = 0x9e,
|
||||
INPUT_BROKER_SEND_PING = 0xaf,
|
||||
@@ -57,9 +58,10 @@ class InputBroker : public Observable<const InputEvent *>
|
||||
|
||||
public:
|
||||
InputBroker();
|
||||
bool menuMode = true;
|
||||
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 +71,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
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
#include "TDeckProKeyboard.h"
|
||||
#elif defined(T_LORA_PAGER)
|
||||
#include "TLoraPagerKeyboard.h"
|
||||
#elif defined(M5STACK_CARDPUTER_ADV)
|
||||
#include "CardputerKeyboard.h"
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
#include "HackadayCommunicatorKeyboard.h"
|
||||
#else
|
||||
#include "TCA8418Keyboard.h"
|
||||
#endif
|
||||
@@ -20,6 +24,10 @@ KbI2cBase::KbI2cBase(const char *name)
|
||||
TCAKeyboard(*(new TDeckProKeyboard()))
|
||||
#elif defined(T_LORA_PAGER)
|
||||
TCAKeyboard(*(new TLoraPagerKeyboard()))
|
||||
#elif defined(M5STACK_CARDPUTER_ADV)
|
||||
TCAKeyboard(*(new CardputerKeyboard()))
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
TCAKeyboard(*(new HackadayCommunicatorKeyboard()))
|
||||
#else
|
||||
TCAKeyboard(*(new TCA8418Keyboard()))
|
||||
#endif
|
||||
|
||||
+29
-29
@@ -23,6 +23,7 @@
|
||||
#include "power.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
#include "detect/ScanI2CConsumer.h"
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#include <Wire.h>
|
||||
#endif
|
||||
@@ -58,12 +59,12 @@ NimbleBluetooth *nimbleBluetooth = nullptr;
|
||||
NRF52Bluetooth *nrf52Bluetooth = nullptr;
|
||||
#endif
|
||||
|
||||
#if HAS_WIFI || defined(USE_WS5500)
|
||||
#if HAS_WIFI || defined(USE_WS5500) || defined(USE_CH390D)
|
||||
#include "mesh/api/WiFiServerAPI.h"
|
||||
#include "mesh/wifi/WiFiAPClient.h"
|
||||
#endif
|
||||
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500)
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
|
||||
#include "mesh/api/ethServerAPI.h"
|
||||
#include "mesh/eth/ethClient.h"
|
||||
#endif
|
||||
@@ -329,7 +330,7 @@ void setup()
|
||||
|
||||
#ifdef WIFI_LED
|
||||
pinMode(WIFI_LED, OUTPUT);
|
||||
digitalWrite(WIFI_LED, LOW);
|
||||
digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON);
|
||||
#endif
|
||||
|
||||
#ifdef BLE_LED
|
||||
@@ -435,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)
|
||||
@@ -718,46 +725,21 @@ void setup()
|
||||
LOG_DEBUG("acc_info = %i", acc_info.type);
|
||||
#endif
|
||||
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BME_680, meshtastic_TelemetrySensorType_BME680);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BME_280, meshtastic_TelemetrySensorType_BME280);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_280, meshtastic_TelemetrySensorType_BMP280);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_3XX, meshtastic_TelemetrySensorType_BMP3XX);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHT31, meshtastic_TelemetrySensorType_SHT31);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHTC3, meshtastic_TelemetrySensorType_SHTC3);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LPS22HB, meshtastic_TelemetrySensorType_LPS22);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::OPT3001, meshtastic_TelemetrySensorType_OPT3001);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90632, meshtastic_TelemetrySensorType_MLX90632);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::CGRADSENS, meshtastic_TelemetrySensorType_RADSENS);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN, meshtastic_TelemetrySensorType_DFROBOT_RAIN);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LTR390UV, meshtastic_TelemetrySensorType_LTR390UV);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DPS310, meshtastic_TelemetrySensorType_DPS310);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2561, meshtastic_TelemetrySensorType_TSL2561);
|
||||
|
||||
i2cScanner.reset();
|
||||
#endif
|
||||
|
||||
#ifdef HAS_SDCARD
|
||||
@@ -865,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)
|
||||
@@ -964,6 +953,12 @@ void setup()
|
||||
// Now that the mesh service is created, create any modules
|
||||
setupModules();
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
// Inform modules about I2C devices
|
||||
ScanI2CCompleted(i2cScanner.get());
|
||||
i2cScanner.reset();
|
||||
#endif
|
||||
|
||||
// warn the user about a low entropy key
|
||||
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
|
||||
LOG_WARN(LOW_ENTROPY_WARNING);
|
||||
@@ -1123,7 +1118,12 @@ void setup()
|
||||
userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS;
|
||||
userConfigNoScreen.longPress = INPUT_BROKER_NONE;
|
||||
userConfigNoScreen.longPressTime = 500;
|
||||
#if defined(ELECROW_ThinkNode_M7)
|
||||
userConfigNoScreen.longLongPressTime = 15 * 1000;
|
||||
userConfigNoScreen.longLongPress = INPUT_BROKER_FACTORY_RST;
|
||||
#else
|
||||
userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN;
|
||||
#endif
|
||||
userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING;
|
||||
userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE;
|
||||
UserButtonThread->initButton(userConfigNoScreen);
|
||||
@@ -1600,7 +1600,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);
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
+39
-63
@@ -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);
|
||||
|
||||
@@ -25,7 +25,7 @@ template class LR11x0Interface<LR1121>;
|
||||
template class SX126xInterface<STM32WLx>;
|
||||
#endif
|
||||
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500)
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
|
||||
#include "api/ethServerAPI.h"
|
||||
template class ServerAPI<EthernetClient>;
|
||||
template class APIServerPort<ethServerAPI, EthernetServer>;
|
||||
|
||||
@@ -218,6 +218,7 @@ template <typename T> void LR11x0Interface<T>::addReceiveMetadata(meshtastic_Mes
|
||||
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
|
||||
mp->rx_snr = lora.getSNR();
|
||||
mp->rx_rssi = lround(lora.getRSSI());
|
||||
LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
|
||||
}
|
||||
|
||||
/** We override to turn on transmitter power as needed.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+52
-53
@@ -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;
|
||||
};
|
||||
+57
-7
@@ -77,6 +77,14 @@ static unsigned char userprefs_admin_key_1[] = USERPREFS_USE_ADMIN_KEY_1;
|
||||
static unsigned char userprefs_admin_key_2[] = USERPREFS_USE_ADMIN_KEY_2;
|
||||
#endif
|
||||
|
||||
// Weak empty variant initialization function.
|
||||
// May be redefined by variant files.
|
||||
void variantDefaultConfig() __attribute__((weak));
|
||||
void variantDefaultConfig() {}
|
||||
|
||||
void variantDefaultModuleConfig() __attribute__((weak));
|
||||
void variantDefaultModuleConfig() {}
|
||||
|
||||
#ifdef HELTEC_MESH_NODE_T114
|
||||
|
||||
uint32_t read8(uint8_t bits, uint8_t dummy, uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_t rst)
|
||||
@@ -653,7 +661,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;
|
||||
@@ -728,6 +736,9 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
config.display.screen_on_secs = 30;
|
||||
config.display.wake_on_tap_or_motion = true;
|
||||
#endif
|
||||
#ifdef COMPASS_ORIENTATION
|
||||
config.display.compass_orientation = COMPASS_ORIENTATION;
|
||||
#endif
|
||||
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
|
||||
if (WiFiOTA::isUpdated()) {
|
||||
WiFiOTA::recoverConfig(&config.network);
|
||||
@@ -740,6 +751,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
#endif
|
||||
|
||||
initConfigIntervals();
|
||||
variantDefaultConfig();
|
||||
variantDefaultModuleConfig();
|
||||
}
|
||||
|
||||
void NodeDB::initConfigIntervals()
|
||||
@@ -795,11 +808,16 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.external_notification.output_ms = 500;
|
||||
moduleConfig.external_notification.nag_timeout = 2;
|
||||
#endif
|
||||
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312)
|
||||
// Default to RAK led pin 2 (blue)
|
||||
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \
|
||||
defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M6)
|
||||
// Default to PIN_LED2 for external notification output (LED color depends on device variant)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.output = PIN_LED2;
|
||||
#if defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3)
|
||||
moduleConfig.external_notification.active = false;
|
||||
#else
|
||||
moduleConfig.external_notification.active = true;
|
||||
#endif
|
||||
moduleConfig.external_notification.alert_message = true;
|
||||
moduleConfig.external_notification.output_ms = 1000;
|
||||
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
||||
@@ -978,12 +996,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 +1663,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;
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
#include "PacketCache.h"
|
||||
#include "Router.h"
|
||||
|
||||
PacketCache packetCache{};
|
||||
|
||||
/**
|
||||
* Allocate a new cache entry and copy the packet header and payload into it
|
||||
*/
|
||||
PacketCacheEntry *PacketCache::cache(const meshtastic_MeshPacket *p, bool preserveMetadata)
|
||||
{
|
||||
size_t payload_size =
|
||||
(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) ? p->encrypted.size : p->decoded.payload.size;
|
||||
PacketCacheEntry *e = (PacketCacheEntry *)malloc(sizeof(PacketCacheEntry) + payload_size +
|
||||
(preserveMetadata ? sizeof(PacketCacheMetadata) : 0));
|
||||
if (!e) {
|
||||
LOG_ERROR("Unable to allocate memory for packet cache entry");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*e = {};
|
||||
e->header.from = p->from;
|
||||
e->header.to = p->to;
|
||||
e->header.id = p->id;
|
||||
e->header.channel = p->channel;
|
||||
e->header.next_hop = p->next_hop;
|
||||
e->header.relay_node = p->relay_node;
|
||||
e->header.flags = (p->hop_limit & PACKET_FLAGS_HOP_LIMIT_MASK) | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) |
|
||||
(p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0) |
|
||||
((p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK);
|
||||
|
||||
PacketCacheMetadata m{};
|
||||
if (preserveMetadata) {
|
||||
e->has_metadata = true;
|
||||
m.rx_rssi = (uint8_t)(p->rx_rssi + 200);
|
||||
m.rx_snr = (uint8_t)((p->rx_snr + 30.0f) / 0.25f);
|
||||
m.rx_time = p->rx_time;
|
||||
m.transport_mechanism = p->transport_mechanism;
|
||||
m.priority = p->priority;
|
||||
}
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
|
||||
e->encrypted = true;
|
||||
e->payload_len = p->encrypted.size;
|
||||
memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->encrypted.bytes, p->encrypted.size);
|
||||
} else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
e->encrypted = false;
|
||||
if (preserveMetadata) {
|
||||
m.portnum = p->decoded.portnum;
|
||||
m.want_response = p->decoded.want_response;
|
||||
m.emoji = p->decoded.emoji;
|
||||
m.bitfield = p->decoded.bitfield;
|
||||
if (p->decoded.reply_id)
|
||||
m.reply_id = p->decoded.reply_id;
|
||||
else if (p->decoded.request_id)
|
||||
m.request_id = p->decoded.request_id;
|
||||
}
|
||||
e->payload_len = p->decoded.payload.size;
|
||||
memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->decoded.payload.bytes, p->decoded.payload.size);
|
||||
} else {
|
||||
LOG_ERROR("Unable to cache packet with unknown payload type %d", p->which_payload_variant);
|
||||
free(e);
|
||||
return NULL;
|
||||
}
|
||||
if (preserveMetadata)
|
||||
memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry) + e->payload_len, &m, sizeof(m));
|
||||
|
||||
size += sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0);
|
||||
insert(e);
|
||||
return e;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dump a list of packets into the provided buffer
|
||||
*/
|
||||
void PacketCache::dump(void *dest, const PacketCacheEntry **entries, size_t num_entries)
|
||||
{
|
||||
unsigned char *pos = (unsigned char *)dest;
|
||||
for (size_t i = 0; i < num_entries; i++) {
|
||||
size_t entry_len =
|
||||
sizeof(PacketCacheEntry) + entries[i]->payload_len + (entries[i]->has_metadata ? sizeof(PacketCacheMetadata) : 0);
|
||||
memcpy(pos, entries[i], entry_len);
|
||||
pos += entry_len;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the length of buffer needed to dump the specified entries
|
||||
*/
|
||||
size_t PacketCache::dumpSize(const PacketCacheEntry **entries, size_t num_entries)
|
||||
{
|
||||
size_t total_size = 0;
|
||||
for (size_t i = 0; i < num_entries; i++) {
|
||||
total_size += sizeof(PacketCacheEntry) + entries[i]->payload_len;
|
||||
if (entries[i]->has_metadata)
|
||||
total_size += sizeof(PacketCacheMetadata);
|
||||
}
|
||||
return total_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a packet in the cache
|
||||
*/
|
||||
PacketCacheEntry *PacketCache::find(NodeNum from, PacketId id)
|
||||
{
|
||||
uint16_t h = PACKET_HASH(from, id);
|
||||
PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)];
|
||||
while (e) {
|
||||
if (e->header.from == from && e->header.id == id)
|
||||
return e;
|
||||
e = e->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a packet in the cache by its hash
|
||||
*/
|
||||
PacketCacheEntry *PacketCache::find(PacketHash h)
|
||||
{
|
||||
PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)];
|
||||
while (e) {
|
||||
if (PACKET_HASH(e->header.from, e->header.id) == h)
|
||||
return e;
|
||||
e = e->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a list of packets from the provided buffer
|
||||
*/
|
||||
bool PacketCache::load(void *src, PacketCacheEntry **entries, size_t num_entries)
|
||||
{
|
||||
memset(entries, 0, sizeof(PacketCacheEntry *) * num_entries);
|
||||
unsigned char *pos = (unsigned char *)src;
|
||||
for (size_t i = 0; i < num_entries; i++) {
|
||||
PacketCacheEntry e{};
|
||||
memcpy(&e, pos, sizeof(PacketCacheEntry));
|
||||
size_t entry_len = sizeof(PacketCacheEntry) + e.payload_len + (e.has_metadata ? sizeof(PacketCacheMetadata) : 0);
|
||||
entries[i] = (PacketCacheEntry *)malloc(entry_len);
|
||||
size += entry_len;
|
||||
if (!entries[i]) {
|
||||
LOG_ERROR("Unable to allocate memory for packet cache entry");
|
||||
for (size_t j = 0; j < i; j++) {
|
||||
size -= sizeof(PacketCacheEntry) + entries[j]->payload_len +
|
||||
(entries[j]->has_metadata ? sizeof(PacketCacheMetadata) : 0);
|
||||
free(entries[j]);
|
||||
entries[j] = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
memcpy(entries[i], pos, entry_len);
|
||||
pos += entry_len;
|
||||
}
|
||||
for (size_t i = 0; i < num_entries; i++)
|
||||
insert(entries[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the cached packet into the provided MeshPacket structure
|
||||
*/
|
||||
void PacketCache::rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (!e || !p)
|
||||
return;
|
||||
|
||||
*p = {};
|
||||
p->from = e->header.from;
|
||||
p->to = e->header.to;
|
||||
p->id = e->header.id;
|
||||
p->channel = e->header.channel;
|
||||
p->next_hop = e->header.next_hop;
|
||||
p->relay_node = e->header.relay_node;
|
||||
p->hop_limit = e->header.flags & PACKET_FLAGS_HOP_LIMIT_MASK;
|
||||
p->want_ack = !!(e->header.flags & PACKET_FLAGS_WANT_ACK_MASK);
|
||||
p->via_mqtt = !!(e->header.flags & PACKET_FLAGS_VIA_MQTT_MASK);
|
||||
p->hop_start = (e->header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
|
||||
p->which_payload_variant = e->encrypted ? meshtastic_MeshPacket_encrypted_tag : meshtastic_MeshPacket_decoded_tag;
|
||||
|
||||
unsigned char *payload = ((unsigned char *)e) + sizeof(PacketCacheEntry);
|
||||
PacketCacheMetadata m{};
|
||||
if (e->has_metadata) {
|
||||
memcpy(&m, (payload + e->payload_len), sizeof(m));
|
||||
p->rx_rssi = ((int)m.rx_rssi) - 200;
|
||||
p->rx_snr = ((float)m.rx_snr * 0.25f) - 30.0f;
|
||||
p->rx_time = m.rx_time;
|
||||
p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)m.transport_mechanism;
|
||||
p->priority = (meshtastic_MeshPacket_Priority)m.priority;
|
||||
}
|
||||
if (e->encrypted) {
|
||||
memcpy(p->encrypted.bytes, payload, e->payload_len);
|
||||
p->encrypted.size = e->payload_len;
|
||||
} else {
|
||||
memcpy(p->decoded.payload.bytes, payload, e->payload_len);
|
||||
p->decoded.payload.size = e->payload_len;
|
||||
if (e->has_metadata) {
|
||||
// Decrypted-only metadata
|
||||
p->decoded.portnum = (meshtastic_PortNum)m.portnum;
|
||||
p->decoded.want_response = m.want_response;
|
||||
p->decoded.emoji = m.emoji;
|
||||
p->decoded.bitfield = m.bitfield;
|
||||
if (m.reply_id)
|
||||
p->decoded.reply_id = m.reply_id;
|
||||
else if (m.request_id)
|
||||
p->decoded.request_id = m.request_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a cache entry
|
||||
*/
|
||||
void PacketCache::release(PacketCacheEntry *e)
|
||||
{
|
||||
if (!e)
|
||||
return;
|
||||
remove(e);
|
||||
size -= sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0);
|
||||
free(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new entry into the hash table
|
||||
*/
|
||||
void PacketCache::insert(PacketCacheEntry *e)
|
||||
{
|
||||
assert(e);
|
||||
PacketHash h = PACKET_HASH(e->header.from, e->header.id);
|
||||
PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)];
|
||||
e->next = *target;
|
||||
*target = e;
|
||||
num_entries++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an entry from the hash table
|
||||
*/
|
||||
void PacketCache::remove(PacketCacheEntry *e)
|
||||
{
|
||||
assert(e);
|
||||
PacketHash h = PACKET_HASH(e->header.from, e->header.id);
|
||||
PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)];
|
||||
while (*target) {
|
||||
if (*target == e) {
|
||||
*target = e->next;
|
||||
e->next = NULL;
|
||||
num_entries--;
|
||||
break;
|
||||
} else {
|
||||
target = &(*target)->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
#include "RadioInterface.h"
|
||||
|
||||
#define PACKET_HASH(a, b) ((((a ^ b) >> 16) ^ (a ^ b)) & 0xFFFF) // 16 bit fold of packet (from, id) tuple
|
||||
typedef uint16_t PacketHash;
|
||||
|
||||
#define PACKET_CACHE_BUCKETS 64 // Number of hash table buckets
|
||||
#define PACKET_CACHE_BUCKET(h) (((h >> 12) ^ (h >> 6) ^ h) & 0x3F) // Fold hash down to 6-bit bucket index
|
||||
|
||||
typedef struct PacketCacheEntry {
|
||||
PacketCacheEntry *next;
|
||||
PacketHeader header;
|
||||
uint16_t payload_len = 0;
|
||||
union {
|
||||
uint16_t bitfield;
|
||||
struct {
|
||||
uint8_t encrypted : 1; // Payload is encrypted
|
||||
uint8_t has_metadata : 1; // Payload includes PacketCacheMetadata
|
||||
uint8_t : 6; // Reserved for future use
|
||||
uint8_t : 8; // Reserved for future use
|
||||
};
|
||||
};
|
||||
} PacketCacheEntry;
|
||||
|
||||
typedef struct PacketCacheMetadata {
|
||||
PacketCacheMetadata() : _bitfield(0), reply_id(0), _bitfield2(0) {}
|
||||
union {
|
||||
uint32_t _bitfield;
|
||||
struct {
|
||||
uint16_t portnum : 9; // meshtastic_MeshPacket::decoded::portnum
|
||||
uint16_t want_response : 1; // meshtastic_MeshPacket::decoded::want_response
|
||||
uint16_t emoji : 1; // meshtastic_MeshPacket::decoded::emoji
|
||||
uint16_t bitfield : 5; // meshtastic_MeshPacket::decoded::bitfield (truncated)
|
||||
uint8_t rx_rssi : 8; // meshtastic_MeshPacket::rx_rssi (map via actual RSSI + 200)
|
||||
uint8_t rx_snr : 8; // meshtastic_MeshPacket::rx_snr (map via (p->rx_snr + 30.0f) / 0.25f)
|
||||
};
|
||||
};
|
||||
union {
|
||||
uint32_t reply_id; // meshtastic_MeshPacket::decoded.reply_id
|
||||
uint32_t request_id; // meshtastic_MeshPacket::decoded.request_id
|
||||
};
|
||||
uint32_t rx_time = 0; // meshtastic_MeshPacket::rx_time
|
||||
uint8_t transport_mechanism = 0; // meshtastic_MeshPacket::transport_mechanism
|
||||
struct {
|
||||
uint8_t _bitfield2;
|
||||
union {
|
||||
uint8_t priority : 7; // meshtastic_MeshPacket::priority
|
||||
uint8_t reserved : 1; // Reserved for future use
|
||||
};
|
||||
};
|
||||
} PacketCacheMetadata;
|
||||
|
||||
class PacketCache
|
||||
{
|
||||
public:
|
||||
PacketCacheEntry *cache(const meshtastic_MeshPacket *p, bool preserveMetadata);
|
||||
static void dump(void *dest, const PacketCacheEntry **entries, size_t num_entries);
|
||||
size_t dumpSize(const PacketCacheEntry **entries, size_t num_entries);
|
||||
PacketCacheEntry *find(NodeNum from, PacketId id);
|
||||
PacketCacheEntry *find(PacketHash h);
|
||||
bool load(void *src, PacketCacheEntry **entries, size_t num_entries);
|
||||
size_t getNumEntries() { return num_entries; }
|
||||
size_t getSize() { return size; }
|
||||
void rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p);
|
||||
void release(PacketCacheEntry *e);
|
||||
|
||||
private:
|
||||
PacketCacheEntry *buckets[PACKET_CACHE_BUCKETS]{};
|
||||
size_t num_entries = 0;
|
||||
size_t size = 0;
|
||||
void insert(PacketCacheEntry *e);
|
||||
void remove(PacketCacheEntry *e);
|
||||
};
|
||||
|
||||
extern PacketCache packetCache;
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+134
-31
@@ -15,6 +15,7 @@
|
||||
#include "Router.h"
|
||||
#include "SPILock.h"
|
||||
#include "TypeConversions.h"
|
||||
#include "concurrency/LockGuard.h"
|
||||
#include "main.h"
|
||||
#include "xmodem.h"
|
||||
|
||||
@@ -56,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
|
||||
@@ -70,14 +74,31 @@ void PhoneAPI::handleStartConfig()
|
||||
spiLock->unlock();
|
||||
LOG_DEBUG("Got %d files in manifest", filesManifest.size());
|
||||
|
||||
LOG_INFO("Start API client config");
|
||||
nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos
|
||||
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);
|
||||
nodeInfoForPhone = {};
|
||||
nodeInfoQueue.clear();
|
||||
}
|
||||
resetReadIndex();
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -93,7 +114,12 @@ void PhoneAPI::close()
|
||||
onConnectionChanged(false);
|
||||
fromRadioScratch = {};
|
||||
toRadioScratch = {};
|
||||
nodeInfoForPhone = {};
|
||||
// Clear cached node info under lock because NimBLE callbacks can still be draining it.
|
||||
{
|
||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||
nodeInfoForPhone = {};
|
||||
nodeInfoQueue.clear();
|
||||
}
|
||||
packetForPhone = NULL;
|
||||
filesManifest.clear();
|
||||
fromRadioNum = 0;
|
||||
@@ -148,6 +174,10 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
case meshtastic_ToRadio_mqttClientProxyMessage_tag:
|
||||
LOG_DEBUG("Got MqttClientProxy message");
|
||||
if (state != STATE_SEND_PACKETS) {
|
||||
LOG_WARN("Ignore MqttClientProxy message while completing config handshake");
|
||||
break;
|
||||
}
|
||||
if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled &&
|
||||
(channels.anyMqttEnabled() || moduleConfig.mqtt.map_reporting_enabled)) {
|
||||
mqtt->onClientProxyReceive(toRadioScratch.mqttClientProxyMessage);
|
||||
@@ -239,13 +269,20 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
LOG_DEBUG("Send My NodeInfo");
|
||||
auto us = nodeDB->readNextMeshNode(readIndex);
|
||||
if (us) {
|
||||
nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(us);
|
||||
nodeInfoForPhone.has_hops_away = false;
|
||||
nodeInfoForPhone.is_favorite = true;
|
||||
auto info = TypeConversions::ConvertToNodeInfo(us);
|
||||
info.has_hops_away = false;
|
||||
info.is_favorite = true;
|
||||
{
|
||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||
nodeInfoForPhone = info;
|
||||
}
|
||||
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
|
||||
fromRadioScratch.node_info = nodeInfoForPhone;
|
||||
fromRadioScratch.node_info = info;
|
||||
// Should allow us to resume sending NodeInfo in STATE_SEND_OTHER_NODEINFOS
|
||||
nodeInfoForPhone.num = 0;
|
||||
{
|
||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||
nodeInfoForPhone.num = 0;
|
||||
}
|
||||
}
|
||||
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
|
||||
// If client only wants node info, jump directly to sending nodes
|
||||
@@ -431,17 +468,44 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
break;
|
||||
|
||||
case STATE_SEND_OTHER_NODEINFOS: {
|
||||
if (nodeInfoForPhone.num != 0) {
|
||||
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);
|
||||
if (nodeInfoForPhone.num == 0 && !nodeInfoQueue.empty()) {
|
||||
// Serve the next cached node without re-reading from the DB iterator.
|
||||
nodeInfoForPhone = nodeInfoQueue.front();
|
||||
nodeInfoQueue.pop_front();
|
||||
}
|
||||
infoToSend = nodeInfoForPhone;
|
||||
if (infoToSend.num != 0)
|
||||
nodeInfoForPhone = {};
|
||||
}
|
||||
|
||||
if (infoToSend.num != 0) {
|
||||
// Just in case we stored a different user.id in the past, but should never happen going forward
|
||||
sprintf(nodeInfoForPhone.user.id, "!%08x", nodeInfoForPhone.num);
|
||||
LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
|
||||
nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
|
||||
sprintf(infoToSend.user.id, "!%08x", infoToSend.num);
|
||||
|
||||
// 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 = nodeInfoForPhone;
|
||||
// Stay in current state until done sending nodeinfos
|
||||
nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time
|
||||
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;
|
||||
// Go ahead and send that ID right now
|
||||
return getFromRadio(buf);
|
||||
@@ -521,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;
|
||||
}
|
||||
|
||||
@@ -545,6 +626,33 @@ void PhoneAPI::releaseQueueStatusPhonePacket()
|
||||
}
|
||||
}
|
||||
|
||||
void PhoneAPI::prefetchNodeInfos()
|
||||
{
|
||||
bool added = false;
|
||||
// Keep the queue topped up so BLE reads stay responsive even if DB fetches take a moment.
|
||||
{
|
||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||
while (nodeInfoQueue.size() < kNodePrefetchDepth) {
|
||||
auto nextNode = nodeDB->readNextMeshNode(readIndex);
|
||||
if (!nextNode)
|
||||
break;
|
||||
|
||||
auto info = TypeConversions::ConvertToNodeInfo(nextNode);
|
||||
bool isUs = info.num == nodeDB->getNodeNum();
|
||||
info.hops_away = isUs ? 0 : info.hops_away;
|
||||
info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
|
||||
info.snr = isUs ? 0 : info.snr;
|
||||
info.via_mqtt = isUs ? false : info.via_mqtt;
|
||||
info.is_favorite = info.is_favorite || isUs;
|
||||
nodeInfoQueue.push_back(info);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (added)
|
||||
onNowHasData(0);
|
||||
}
|
||||
|
||||
void PhoneAPI::releaseMqttClientProxyPhonePacket()
|
||||
{
|
||||
if (mqttClientProxyMessageForPhone) {
|
||||
@@ -580,22 +688,17 @@ bool PhoneAPI::available()
|
||||
case STATE_SEND_COMPLETE_ID:
|
||||
return true;
|
||||
|
||||
case STATE_SEND_OTHER_NODEINFOS:
|
||||
if (nodeInfoForPhone.num == 0) {
|
||||
auto nextNode = nodeDB->readNextMeshNode(readIndex);
|
||||
if (nextNode) {
|
||||
nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode);
|
||||
bool isUs = nodeInfoForPhone.num == nodeDB->getNodeNum();
|
||||
nodeInfoForPhone.hops_away = isUs ? 0 : nodeInfoForPhone.hops_away;
|
||||
nodeInfoForPhone.last_heard = isUs ? getValidTime(RTCQualityFromNet) : nodeInfoForPhone.last_heard;
|
||||
nodeInfoForPhone.snr = isUs ? 0 : nodeInfoForPhone.snr;
|
||||
nodeInfoForPhone.via_mqtt = isUs ? false : nodeInfoForPhone.via_mqtt;
|
||||
nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || isUs; // Our node is always a favorite
|
||||
|
||||
onNowHasData(0);
|
||||
}
|
||||
case STATE_SEND_OTHER_NODEINFOS: {
|
||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||
if (nodeInfoQueue.empty()) {
|
||||
// Drop the lock before prefetching; prefetchNodeInfos() will re-acquire it.
|
||||
goto PREFETCH_NODEINFO;
|
||||
}
|
||||
}
|
||||
return true; // Always say we have something, because we might need to advance our state machine
|
||||
PREFETCH_NODEINFO:
|
||||
prefetchNodeInfos();
|
||||
return true;
|
||||
case STATE_SEND_PACKETS: {
|
||||
if (!queueStatusPacketForPhone)
|
||||
queueStatusPacketForPhone = service->getQueueStatusForPhone();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "Observer.h"
|
||||
#include "concurrency/Lock.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshtastic/portnums.pb.h"
|
||||
#include <deque>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -79,6 +81,12 @@ class PhoneAPI
|
||||
|
||||
/// We temporarily keep the nodeInfo here between the call to available and getFromRadio
|
||||
meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default;
|
||||
// Prefetched node info entries ready for immediate transmission to the phone.
|
||||
std::deque<meshtastic_NodeInfo> nodeInfoQueue;
|
||||
// Tunable size of the node info cache so we can keep BLE reads non-blocking.
|
||||
static constexpr size_t kNodePrefetchDepth = 4;
|
||||
// Protect nodeInfoForPhone + nodeInfoQueue because NimBLE callbacks run in a separate FreeRTOS task.
|
||||
concurrency::Lock nodeInfoMutex;
|
||||
|
||||
meshtastic_ToRadio toRadioScratch = {
|
||||
0}; // this is a static scratch object, any data must be copied elsewhere before returning
|
||||
@@ -128,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
|
||||
@@ -150,14 +159,33 @@ 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();
|
||||
|
||||
void releaseQueueStatusPhonePacket();
|
||||
|
||||
void prefetchNodeInfos();
|
||||
|
||||
void releaseMqttClientProxyPhonePacket();
|
||||
|
||||
void releaseClientNotification();
|
||||
|
||||
@@ -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()
|
||||
|
||||
+14
-13
@@ -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);
|
||||
}
|
||||
@@ -647,23 +647,24 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
}
|
||||
|
||||
#ifndef NUM_PA_POINTS
|
||||
if (TX_GAIN_LORA > 0) {
|
||||
if (TX_GAIN_LORA > 0 && !devicestate.owner.is_licensed) {
|
||||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA);
|
||||
power -= TX_GAIN_LORA;
|
||||
}
|
||||
#else
|
||||
// we have an array of PA gain values. Find the highest power setting that works.
|
||||
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
|
||||
for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
|
||||
if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
|
||||
((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
|
||||
// we've exceeded the power limit, or hit the max we can do
|
||||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
|
||||
power -= tx_gain[radio_dbm];
|
||||
break;
|
||||
if (!devicestate.owner.is_licensed) {
|
||||
// we have an array of PA gain values. Find the highest power setting that works.
|
||||
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
|
||||
for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
|
||||
if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
|
||||
((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
|
||||
// we've exceeded the power limit, or hit the max we can do
|
||||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
|
||||
power -= tx_gain[radio_dbm];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
if (power > loraMaxPower) // Clamp power to maximum defined level
|
||||
power = loraMaxPower;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -411,8 +406,15 @@ void RadioLibInterface::completeSending()
|
||||
// that can take a long time
|
||||
auto p = sendingPacket;
|
||||
sendingPacket = NULL;
|
||||
#ifdef LED_LORA
|
||||
digitalWrite(LED_LORA, LED_STATE_OFF);
|
||||
#endif
|
||||
|
||||
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++;
|
||||
@@ -560,6 +562,9 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
|
||||
enableInterrupt(isrTxLevel0);
|
||||
lastTxStart = millis();
|
||||
printPacket("Started Tx", txp);
|
||||
#ifdef LED_LORA
|
||||
digitalWrite(LED_LORA, LED_STATE_ON);
|
||||
#endif
|
||||
}
|
||||
|
||||
return res == RADIOLIB_ERR_NONE;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -266,6 +266,7 @@ template <typename T> void SX126xInterface<T>::addReceiveMetadata(meshtastic_Mes
|
||||
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
|
||||
mp->rx_snr = lora.getSNR();
|
||||
mp->rx_rssi = lround(lora.getRSSI());
|
||||
LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
|
||||
}
|
||||
|
||||
/** We override to turn on transmitter power as needed.
|
||||
|
||||
@@ -204,6 +204,7 @@ template <typename T> void SX128xInterface<T>::addReceiveMetadata(meshtastic_Mes
|
||||
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
|
||||
mp->rx_snr = lora.getSNR();
|
||||
mp->rx_rssi = lround(lora.getRSSI());
|
||||
LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
|
||||
}
|
||||
|
||||
/** We override to turn on transmitter power as needed.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user