mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-15 15:22:34 +00:00
Compare commits
314 Commits
adaptive-t
...
v2.7.13.59
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a124b7f3d | ||
|
|
7def82595d | ||
|
|
3a67204f6d | ||
|
|
d1b66782d1 | ||
|
|
718fd118b0 | ||
|
|
75f7ded12c | ||
|
|
bca0e1abde | ||
|
|
c46abe125c | ||
|
|
7f78a624cd | ||
|
|
17324fa725 | ||
|
|
001654e90a | ||
|
|
16b1280804 | ||
|
|
d00fda2f4d | ||
|
|
4f817d69eb | ||
|
|
de83b448f9 | ||
|
|
c145be8e05 | ||
|
|
756efa7f00 | ||
|
|
0dfa11a909 | ||
|
|
c330bfe848 | ||
|
|
28f53d132a | ||
|
|
7d3e529b2f | ||
|
|
f045ca2303 | ||
|
|
b6830a68a0 | ||
|
|
dd51de85f3 | ||
|
|
580fa292ac | ||
|
|
664d17c519 | ||
|
|
13c4c2037d | ||
|
|
95d3ecb239 | ||
|
|
799cf0e8b3 | ||
|
|
35fa418739 | ||
|
|
585d9d36a8 | ||
|
|
b682ab3967 | ||
|
|
49b9d5151d | ||
|
|
3f8707cafe | ||
|
|
39780656ef | ||
|
|
153cf65214 | ||
|
|
07d354fa02 | ||
|
|
f4e93b4a2d | ||
|
|
18c4956aba | ||
|
|
15ee1c2819 | ||
|
|
f4ff210311 | ||
|
|
26747038bb | ||
|
|
8e082686a3 | ||
|
|
871986d183 | ||
|
|
821d8aa15d | ||
|
|
c4656dacf7 | ||
|
|
64d92679d0 | ||
|
|
b5aa16bade | ||
|
|
c740550d16 | ||
|
|
cb3ce1b1a8 | ||
|
|
2ad52812c0 | ||
|
|
5b9563a357 | ||
|
|
126954c2ed | ||
|
|
f6eede8597 | ||
|
|
6f2241751e | ||
|
|
1d4134c08c | ||
|
|
ffb168be00 | ||
|
|
27dc3be14a | ||
|
|
f2a63faddd | ||
|
|
d2403437ff | ||
|
|
05c176c16a | ||
|
|
7afc6ef833 | ||
|
|
68e739359f | ||
|
|
2357ea0042 | ||
|
|
cbdbaf62f1 | ||
|
|
e1c259ae32 | ||
|
|
b4dea63f44 | ||
|
|
30022c9377 | ||
|
|
0283e0658b | ||
|
|
4f142e6766 | ||
|
|
af8407aca9 | ||
|
|
e5d67310d6 | ||
|
|
47df9d4a79 | ||
|
|
4df79374b0 | ||
|
|
0bfc342b48 | ||
|
|
d9905f3c31 | ||
|
|
ee2ed0a8fb | ||
|
|
acab814b6f | ||
|
|
073c35c782 | ||
|
|
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 | ||
|
|
45f15b8fe6 | ||
|
|
2a14696525 | ||
|
|
05edcc5d6c | ||
|
|
828e11cc48 | ||
|
|
adae68fbfe | ||
|
|
7822f28152 | ||
|
|
d332dfa19b | ||
|
|
fe2e2753aa | ||
|
|
fcb1d64eb9 | ||
|
|
0c2673ee2f | ||
|
|
9c5513dcfe | ||
|
|
74e6723ad9 | ||
|
|
e8e8ee0993 | ||
|
|
a7f15097da | ||
|
|
e4ec719e6f | ||
|
|
9b7b8ffb21 | ||
|
|
f0e4ea7664 | ||
|
|
468b40e8db | ||
|
|
bd9076b740 | ||
|
|
81a5aeff74 | ||
|
|
f13e7c20ba | ||
|
|
5bcc47dddb | ||
|
|
668cc9fd64 | ||
|
|
1d5b343836 | ||
|
|
8023f475ee | ||
|
|
b696e083f3 | ||
|
|
b214f09ca1 | ||
|
|
68a2c4adda | ||
|
|
518680514f | ||
|
|
fc1737c949 | ||
|
|
735784e6e4 | ||
|
|
87e3540f48 | ||
|
|
329a494ce2 | ||
|
|
627c0145e7 | ||
|
|
29f4d99bf6 | ||
|
|
036a58735e | ||
|
|
18ca9e80d5 | ||
|
|
5c2997ef53 | ||
|
|
c147ce9a85 | ||
|
|
27f316b931 | ||
|
|
f7cf5e6b0a | ||
|
|
7c373b76c4 | ||
|
|
de6a02756d | ||
|
|
d708ed5908 | ||
|
|
7c4367cddc | ||
|
|
6022b749ba | ||
|
|
cbd30f95f3 | ||
|
|
9ded6a5215 | ||
|
|
1e4bcb04d5 | ||
|
|
c4dff21e5b | ||
|
|
888692a373 | ||
|
|
fe4fb085e6 | ||
|
|
7c5e2bc95a | ||
|
|
ed32650b9b | ||
|
|
1b97cf57ad | ||
|
|
e8296914a5 | ||
|
|
0e38fef5bf | ||
|
|
b7f6a2acb6 | ||
|
|
0c2283e19e | ||
|
|
78d010fd29 | ||
|
|
037e56b1fd | ||
|
|
c7208ca05b | ||
|
|
f72a4c50bd | ||
|
|
775595cb37 | ||
|
|
047600d088 | ||
|
|
560eb2c455 | ||
|
|
1be3820152 | ||
|
|
da98622f59 | ||
|
|
03baad2c11 | ||
|
|
0ddaf710e4 | ||
|
|
50cfe7c705 | ||
|
|
76c1d69560 | ||
|
|
17863e96e2 | ||
|
|
c48a64e183 | ||
|
|
e954591ca5 | ||
|
|
305f513834 | ||
|
|
0860fee209 | ||
|
|
878ac3ec84 | ||
|
|
a62e1cfa3c | ||
|
|
ca02808c5d | ||
|
|
b978c6c86c | ||
|
|
51ad9d0244 | ||
|
|
76d4807130 | ||
|
|
5ec09783c5 | ||
|
|
2eb0fcbcaf | ||
|
|
9bb7bb467b | ||
|
|
de67714248 | ||
|
|
e0cf9130be | ||
|
|
f82667d71e | ||
|
|
1d283523f2 | ||
|
|
ec28c383af | ||
|
|
641a2fc63d | ||
|
|
f7469159cf | ||
|
|
af83670376 | ||
|
|
849bbad279 | ||
|
|
b28d095096 | ||
|
|
17afdb9ccf | ||
|
|
d5164b4fbf | ||
|
|
ad44940732 | ||
|
|
8b466b1db3 | ||
|
|
f9937967fa | ||
|
|
4fd568f384 | ||
|
|
dae9b1c024 | ||
|
|
a8a6644192 | ||
|
|
34a595b88e | ||
|
|
e32ce3fafe | ||
|
|
69c61f8247 | ||
|
|
b08e4efb78 | ||
|
|
ee6857511a | ||
|
|
9df4d57168 | ||
|
|
500e7920ae | ||
|
|
ee8fa9f328 | ||
|
|
02efef3aaf | ||
|
|
0f6131d2c8 | ||
|
|
8d323a1cf1 | ||
|
|
a3e6f16378 | ||
|
|
85fe7d26ed | ||
|
|
777e11bad9 | ||
|
|
7633ddcfd1 | ||
|
|
abc011aeb9 | ||
|
|
a4a6ee1df4 | ||
|
|
6448f069f8 | ||
|
|
f6a28e15d2 | ||
|
|
9b1a118103 | ||
|
|
4dec912a39 | ||
|
|
52ee655fd2 | ||
|
|
79bc286b35 | ||
|
|
1fc8d54d4c | ||
|
|
58602d59bd | ||
|
|
edb5c0f88e | ||
|
|
060a129995 | ||
|
|
a1ca553bc0 | ||
|
|
189aec9fe3 | ||
|
|
2fbfb19304 | ||
|
|
e7840122e8 | ||
|
|
319cd6fa7b | ||
|
|
0db2e40ee3 | ||
|
|
59f9e2a00b | ||
|
|
97d0f3286e | ||
|
|
da4bc0f97c | ||
|
|
c811e4c573 | ||
|
|
d558df8a3a | ||
|
|
4100ba83a3 | ||
|
|
a76cc88dc2 | ||
|
|
bfb03b422a | ||
|
|
54f9f7a591 | ||
|
|
0e26702c46 | ||
|
|
d427b477e3 | ||
|
|
c9cb2cfd94 | ||
|
|
43078a40eb | ||
|
|
4ac99c5df1 | ||
|
|
c9702fe4d0 | ||
|
|
e0f88be2d7 | ||
|
|
1c256ccfd7 | ||
|
|
6c932d51ec | ||
|
|
f0b7aab030 | ||
|
|
5fca3a30ec | ||
|
|
a76f591231 | ||
|
|
20f68929c8 | ||
|
|
42e4759634 | ||
|
|
3d86c99c25 | ||
|
|
bfadd9c866 | ||
|
|
f8d44f8f6c | ||
|
|
ccff2769fe | ||
|
|
e0890b2a13 | ||
|
|
5579d87845 | ||
|
|
693181b2be | ||
|
|
39c663f203 | ||
|
|
71f659cba6 | ||
|
|
4e879a7b26 | ||
|
|
8c9c00172c | ||
|
|
d5300a1141 | ||
|
|
a31fdf01ce | ||
|
|
1594421214 | ||
|
|
02cb306bb1 | ||
|
|
6b7ad9c4e1 | ||
|
|
fa1ccf4779 | ||
|
|
67ecb60bcd | ||
|
|
9da92626e5 |
@@ -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-12
|
||||
|
||||
USER root
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/python:1": {
|
||||
"installTools": true,
|
||||
"version": "latest"
|
||||
"version": "3.13"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
|
||||
2
.github/actions/build-variant/action.yml
vendored
2
.github/actions/build-variant/action.yml
vendored
@@ -100,7 +100,7 @@ runs:
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
|
||||
2
.github/workflows/build_debian_src.yml
vendored
2
.github/workflows/build_debian_src.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
PKG_VERSION: ${{ steps.version.outputs.deb }}
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
overwrite: true
|
||||
|
||||
5
.github/workflows/build_firmware.yml
vendored
5
.github/workflows/build_firmware.yml
vendored
@@ -19,6 +19,8 @@ jobs:
|
||||
pio-build:
|
||||
name: build-${{ inputs.platform }}
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
artifact-id: ${{ steps.upload.outputs.artifact-id }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
@@ -54,7 +56,8 @@ 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
|
||||
overwrite: true
|
||||
|
||||
366
.github/workflows/build_one_arch.yml
vendored
366
.github/workflows/build_one_arch.yml
vendored
@@ -3,6 +3,7 @@ name: Build One Arch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
# trunk-ignore(checkov/CKV_GHA_7)
|
||||
arch:
|
||||
type: choice
|
||||
options:
|
||||
@@ -16,10 +17,13 @@ on:
|
||||
- stm32
|
||||
- native
|
||||
|
||||
permissions: read-all
|
||||
|
||||
env:
|
||||
INPUT_ARCH: ${{ github.event.inputs.arch }}
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
@@ -31,23 +35,11 @@ jobs:
|
||||
- name: Generate matrix
|
||||
id: jsonStep
|
||||
run: |
|
||||
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra)
|
||||
else
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr)
|
||||
fi
|
||||
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
|
||||
echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
|
||||
TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra)
|
||||
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
|
||||
echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
esp32: ${{ steps.jsonStep.outputs.esp32 }}
|
||||
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
|
||||
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
|
||||
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
|
||||
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
|
||||
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
|
||||
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
|
||||
stm32: ${{ steps.jsonStep.outputs.stm32 }}
|
||||
check: ${{ steps.jsonStep.outputs.check }}
|
||||
selected_arch: ${{ steps.jsonStep.outputs.selected_arch }}
|
||||
|
||||
version:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -64,101 +56,18 @@ jobs:
|
||||
long: ${{ steps.version.outputs.long }}
|
||||
deb: ${{ steps.version.outputs.deb }}
|
||||
|
||||
build-esp32:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}}
|
||||
build:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
|
||||
matrix:
|
||||
build: ${{ fromJson(needs.setup.outputs.selected_arch) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32
|
||||
|
||||
build-esp32s3:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}}
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32s3
|
||||
|
||||
build-esp32c3:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}}
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32c3
|
||||
|
||||
build-esp32c6:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}}
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32c6
|
||||
|
||||
build-nrf52840:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}}
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: nrf52840
|
||||
|
||||
build-rp2040:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}}
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: rp2040
|
||||
|
||||
build-rp2350:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}}
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: rp2350
|
||||
|
||||
build-stm32:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }}
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: stm32
|
||||
pio_env: ${{ matrix.build.board }}
|
||||
platform: ${{ matrix.build.arch }}
|
||||
|
||||
build-debian-src:
|
||||
if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
|
||||
@@ -179,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
|
||||
@@ -252,18 +105,7 @@ jobs:
|
||||
- rp2350
|
||||
- stm32
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
version,
|
||||
build-esp32,
|
||||
build-esp32s3,
|
||||
build-esp32c3,
|
||||
build-esp32c6,
|
||||
build-nrf52840,
|
||||
build-rp2040,
|
||||
build-rp2350,
|
||||
build-stm32,
|
||||
]
|
||||
needs: [version, build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
@@ -271,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}}-*
|
||||
@@ -284,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
|
||||
@@ -300,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
|
||||
@@ -319,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
|
||||
@@ -332,169 +174,3 @@ jobs:
|
||||
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
|
||||
description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-artifacts:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
needs:
|
||||
- version
|
||||
- gather-artifacts
|
||||
- build-debian-src
|
||||
- package-pio-deps-native-tft
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
id: create_release
|
||||
with:
|
||||
draft: true
|
||||
prerelease: true
|
||||
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
|
||||
tag_name: v${{ needs.version.outputs.long }}
|
||||
body: |
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||
merge-multiple: true
|
||||
path: ./output/debian-src
|
||||
|
||||
- name: Download `native-tft` pio deps
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./output/pio-deps-native-tft
|
||||
|
||||
- name: Zip Linux sources
|
||||
working-directory: output
|
||||
run: |
|
||||
zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
|
||||
zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
|
||||
|
||||
# For diagnostics
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Add Linux sources to GtiHub Release
|
||||
# Only run when targeting master branch with workflow_dispatch
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
run: |
|
||||
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
|
||||
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-firmware:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- esp32
|
||||
- esp32s3
|
||||
- esp32c3
|
||||
- esp32c6
|
||||
- nrf52840
|
||||
- rp2040
|
||||
- rp2350
|
||||
- stm32
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
needs: [release-artifacts, version]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./output
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Device scripts permissions
|
||||
run: |
|
||||
chmod +x ./output/device-install.sh
|
||||
chmod +x ./output/device-update.sh
|
||||
|
||||
- name: Zip firmware
|
||||
run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
path: ./elfs
|
||||
|
||||
- name: Zip debug elfs
|
||||
run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
|
||||
|
||||
# For diagnostics
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Add bins and debug elfs to GitHub Release
|
||||
# Only run when targeting master branch with workflow_dispatch
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
run: |
|
||||
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
publish-firmware:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
needs: [release-firmware, version]
|
||||
env:
|
||||
targets: |-
|
||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./publish
|
||||
|
||||
- name: Publish firmware to meshtastic.github.io
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
env:
|
||||
# On event/* branches, use the event name as the destination prefix
|
||||
DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
|
||||
with:
|
||||
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
|
||||
external_repository: meshtastic/meshtastic.github.io
|
||||
publish_branch: master
|
||||
publish_dir: ./publish
|
||||
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
|
||||
keep_files: true
|
||||
user_name: github-actions[bot]
|
||||
user_email: github-actions[bot]@users.noreply.github.com
|
||||
commit_message: ${{ needs.version.outputs.long }}
|
||||
enable_jekyll: true
|
||||
|
||||
237
.github/workflows/build_one_target.yml
vendored
237
.github/workflows/build_one_target.yml
vendored
@@ -3,6 +3,7 @@ name: Build One Target
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
# trunk-ignore(checkov/CKV_GHA_7)
|
||||
arch:
|
||||
type: choice
|
||||
options:
|
||||
@@ -19,11 +20,13 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
|
||||
|
||||
# find-target:
|
||||
# type: boolean
|
||||
# default: true
|
||||
# description: 'Find the available targets'
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
find-targets:
|
||||
if: ${{ inputs.target == '' }}
|
||||
@@ -51,13 +54,13 @@ jobs:
|
||||
- name: Generate matrix
|
||||
id: jsonStep
|
||||
run: |
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra)
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra)
|
||||
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Targets:" >> $GITHUB_STEP_SUMMARY
|
||||
echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY
|
||||
echo $TARGETS >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
version:
|
||||
if: ${{ inputs.target != '' }}
|
||||
@@ -75,11 +78,9 @@ jobs:
|
||||
long: ${{ steps.version.outputs.long }}
|
||||
deb: ${{ steps.version.outputs.deb }}
|
||||
|
||||
build-arch:
|
||||
build:
|
||||
if: ${{ inputs.target != '' && inputs.arch != 'native' }}
|
||||
needs: [version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
@@ -105,70 +106,12 @@ jobs:
|
||||
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }}
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
|
||||
docker-deb-amd64:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-deb-amd64-tft:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
pio_env: native-tft
|
||||
|
||||
docker-alp-amd64:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
|
||||
docker-alp-amd64-tft:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: alpine
|
||||
platform: linux/amd64
|
||||
runs-on: ubuntu-24.04
|
||||
push: false
|
||||
pio_env: native-tft
|
||||
|
||||
docker-deb-arm64:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: false
|
||||
|
||||
docker-deb-armv7:
|
||||
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: debian
|
||||
platform: linux/arm/v7
|
||||
runs-on: ubuntu-24.04-arm
|
||||
push: false
|
||||
|
||||
gather-artifacts:
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
needs: [version, build-arch]
|
||||
needs: [version, build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
@@ -176,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-*-*
|
||||
@@ -189,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
|
||||
@@ -205,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
|
||||
@@ -224,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
|
||||
@@ -237,159 +180,3 @@ jobs:
|
||||
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
|
||||
description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-artifacts:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
needs:
|
||||
- version
|
||||
- gather-artifacts
|
||||
- build-debian-src
|
||||
- package-pio-deps-native-tft
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
id: create_release
|
||||
with:
|
||||
draft: true
|
||||
prerelease: true
|
||||
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
|
||||
tag_name: v${{ needs.version.outputs.long }}
|
||||
body: |
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||
merge-multiple: true
|
||||
path: ./output/debian-src
|
||||
|
||||
- name: Download `native-tft` pio deps
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./output/pio-deps-native-tft
|
||||
|
||||
- name: Zip Linux sources
|
||||
working-directory: output
|
||||
run: |
|
||||
zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
|
||||
zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
|
||||
|
||||
# For diagnostics
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Add Linux sources to GtiHub Release
|
||||
# Only run when targeting master branch with workflow_dispatch
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
run: |
|
||||
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
|
||||
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-firmware:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
|
||||
needs: [release-artifacts, version]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: firmware-*-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./output
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Device scripts permissions
|
||||
run: |
|
||||
chmod +x ./output/device-install.sh
|
||||
chmod +x ./output/device-update.sh
|
||||
|
||||
- name: Zip firmware
|
||||
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
path: ./elfs
|
||||
|
||||
- name: Zip debug elfs
|
||||
run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs
|
||||
|
||||
# For diagnostics
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Add bins and debug elfs to GitHub Release
|
||||
# Only run when targeting master branch with workflow_dispatch
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
run: |
|
||||
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
|
||||
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
publish-firmware:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }}
|
||||
needs: [release-firmware, version]
|
||||
env:
|
||||
targets: |-
|
||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./publish
|
||||
|
||||
- name: Publish firmware to meshtastic.github.io
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
env:
|
||||
# On event/* branches, use the event name as the destination prefix
|
||||
DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
|
||||
with:
|
||||
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
|
||||
external_repository: meshtastic/meshtastic.github.io
|
||||
publish_branch: master
|
||||
publish_dir: ./publish
|
||||
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
|
||||
keep_files: true
|
||||
user_name: github-actions[bot]
|
||||
user_email: github-actions[bot]@users.noreply.github.com
|
||||
commit_message: ${{ needs.version.outputs.long }}
|
||||
enable_jekyll: true
|
||||
|
||||
229
.github/workflows/main_matrix.yml
vendored
229
.github/workflows/main_matrix.yml
vendored
@@ -27,19 +27,11 @@ on:
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
arch:
|
||||
- esp32
|
||||
- esp32s3
|
||||
- esp32c3
|
||||
- esp32c6
|
||||
- nrf52840
|
||||
- rp2040
|
||||
- rp2350
|
||||
- stm32
|
||||
- all
|
||||
- check
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
@@ -49,33 +41,22 @@ jobs:
|
||||
python-version: 3.x
|
||||
cache: pip
|
||||
- run: pip install -U platformio
|
||||
- name: Uncomment build epoch
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
|
||||
- name: Generate matrix
|
||||
id: jsonStep
|
||||
run: |
|
||||
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
|
||||
else
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
|
||||
fi
|
||||
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
|
||||
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
|
||||
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
|
||||
echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
|
||||
echo "$TARGETS" >> $GITHUB_STEP_SUMMARY
|
||||
outputs:
|
||||
esp32: ${{ steps.jsonStep.outputs.esp32 }}
|
||||
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
|
||||
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
|
||||
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
|
||||
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
|
||||
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
|
||||
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
|
||||
stm32: ${{ steps.jsonStep.outputs.stm32 }}
|
||||
all: ${{ steps.jsonStep.outputs.all }}
|
||||
check: ${{ steps.jsonStep.outputs.check }}
|
||||
|
||||
version:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
@@ -94,7 +75,8 @@ jobs:
|
||||
needs: setup
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.check) }}
|
||||
matrix:
|
||||
check: ${{ fromJson(needs.setup.outputs.check) }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
||||
@@ -103,96 +85,20 @@ jobs:
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
- name: Check ${{ matrix.board }}
|
||||
run: bin/check-all.sh ${{ matrix.board }}
|
||||
- name: Check ${{ matrix.check.board }}
|
||||
run: bin/check-all.sh ${{ matrix.check.board }}
|
||||
|
||||
build-esp32:
|
||||
build:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
|
||||
matrix:
|
||||
build: ${{ fromJson(needs.setup.outputs.all) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32
|
||||
|
||||
build-esp32s3:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32s3
|
||||
|
||||
build-esp32c3:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32c3
|
||||
|
||||
build-esp32c6:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32c6
|
||||
|
||||
build-nrf52840:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: nrf52840
|
||||
|
||||
build-rp2040:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: rp2040
|
||||
|
||||
build-rp2350:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: rp2350
|
||||
|
||||
build-stm32:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: stm32
|
||||
pio_env: ${{ matrix.build.board }}
|
||||
platform: ${{ matrix.build.platform }}
|
||||
|
||||
build-debian-src:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
@@ -203,7 +109,7 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
package-pio-deps-native-tft:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
if: ${{ github.repository == 'meshtastic/firmware' && github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/package_pio_deps.yml
|
||||
with:
|
||||
pio_env: native-tft
|
||||
@@ -213,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:
|
||||
@@ -288,18 +160,7 @@ jobs:
|
||||
- rp2350
|
||||
- stm32
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
version,
|
||||
build-esp32,
|
||||
build-esp32s3,
|
||||
build-esp32c3,
|
||||
build-esp32c6,
|
||||
build-nrf52840,
|
||||
build-rp2040,
|
||||
build-rp2350,
|
||||
build-stm32,
|
||||
]
|
||||
needs: [version, build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
@@ -307,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}}-*
|
||||
@@ -320,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
|
||||
@@ -336,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
|
||||
@@ -355,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
|
||||
@@ -400,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
|
||||
@@ -457,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
|
||||
@@ -474,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
|
||||
@@ -512,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
|
||||
|
||||
216
.github/workflows/merge_queue.yml
vendored
216
.github/workflows/merge_queue.yml
vendored
@@ -7,23 +7,13 @@ on:
|
||||
# Merge group is a special trigger that is used to trigger the workflow when a merge group is created.
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
FAIL_FAST_PER_ARCH: true
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
arch:
|
||||
- esp32
|
||||
- esp32s3
|
||||
- esp32c3
|
||||
- esp32c6
|
||||
- nrf52840
|
||||
- rp2040
|
||||
- rp2350
|
||||
- stm32
|
||||
- all
|
||||
- check
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
@@ -39,19 +29,12 @@ jobs:
|
||||
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
|
||||
else
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
|
||||
fi
|
||||
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
|
||||
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
|
||||
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
|
||||
echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
esp32: ${{ steps.jsonStep.outputs.esp32 }}
|
||||
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
|
||||
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
|
||||
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
|
||||
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
|
||||
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
|
||||
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
|
||||
stm32: ${{ steps.jsonStep.outputs.stm32 }}
|
||||
all: ${{ steps.jsonStep.outputs.all }}
|
||||
check: ${{ steps.jsonStep.outputs.check }}
|
||||
|
||||
version:
|
||||
@@ -73,7 +56,8 @@ jobs:
|
||||
needs: setup
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix: ${{ fromJson(needs.setup.outputs.check) }}
|
||||
matrix:
|
||||
check: ${{ fromJson(needs.setup.outputs.check) }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
@@ -82,96 +66,19 @@ jobs:
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
- name: Check ${{ matrix.board }}
|
||||
run: bin/check-all.sh ${{ matrix.board }}
|
||||
- name: Check ${{ matrix.check.board }}
|
||||
run: bin/check-all.sh ${{ matrix.check.board }}
|
||||
|
||||
build-esp32:
|
||||
build:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
|
||||
matrix:
|
||||
build: ${{ fromJson(needs.setup.outputs.all) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32
|
||||
|
||||
build-esp32s3:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32s3
|
||||
|
||||
build-esp32c3:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32c3
|
||||
|
||||
build-esp32c6:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: esp32c6
|
||||
|
||||
build-nrf52840:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: nrf52840
|
||||
|
||||
build-rp2040:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: rp2040
|
||||
|
||||
build-rp2350:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: rp2350
|
||||
|
||||
build-stm32:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.board }}
|
||||
platform: stm32
|
||||
pio_env: ${{ matrix.build.board }}
|
||||
platform: ${{ matrix.build.platform }}
|
||||
|
||||
build-debian-src:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
@@ -192,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:
|
||||
@@ -260,18 +139,7 @@ jobs:
|
||||
- rp2350
|
||||
- stm32
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
version,
|
||||
build-esp32,
|
||||
build-esp32s3,
|
||||
build-esp32c3,
|
||||
build-esp32c6,
|
||||
build-nrf52840,
|
||||
build-rp2040,
|
||||
build-rp2350,
|
||||
build-stm32,
|
||||
]
|
||||
needs: [version, build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
@@ -279,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}}-*
|
||||
@@ -292,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
|
||||
@@ -308,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
|
||||
@@ -327,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
|
||||
@@ -372,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
|
||||
@@ -429,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
|
||||
@@ -446,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
|
||||
@@ -484,7 +352,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/package_obs.yml
vendored
2
.github/workflows/package_obs.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/package_pio_deps.yml
vendored
2
.github/workflows/package_pio_deps.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
PLATFORMIO_CORE_DIR: pio/core
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
|
||||
overwrite: true
|
||||
|
||||
2
.github/workflows/package_ppa.yml
vendored
2
.github/workflows/package_ppa.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/pr_tests.yml
vendored
2
.github/workflows/pr_tests.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Download test artifacts
|
||||
if: needs.native-tests.result != 'skipped'
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
|
||||
4
.github/workflows/sec_sast_semgrep_cron.yml
vendored
4
.github/workflows/sec_sast_semgrep_cron.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
# step 3
|
||||
- name: save report as pipeline artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: report.sarif
|
||||
overwrite: true
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
# step 4
|
||||
- name: publish code scanning alerts
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: report.sarif
|
||||
category: semgrep
|
||||
|
||||
8
.github/workflows/stale_bot.yml
vendored
8
.github/workflows/stale_bot.yml
vendored
@@ -17,8 +17,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Stale PR+Issues
|
||||
uses: actions/stale@v10.0.0
|
||||
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
|
||||
|
||||
14
.github/workflows/test_native.yml
vendored
14
.github/workflows/test_native.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Integration test
|
||||
run: |
|
||||
.pio/build/coverage/program &
|
||||
.pio/build/coverage/program -s &
|
||||
PID=$!
|
||||
timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
|
||||
echo "Simulator started, launching python test..."
|
||||
@@ -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,7 +137,7 @@ 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
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
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
|
||||
|
||||
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@@ -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
|
||||
|
||||
@@ -4,24 +4,24 @@ 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.471
|
||||
- renovate@41.130.1
|
||||
- checkov@3.2.486
|
||||
- renovate@41.157.0
|
||||
- prettier@3.6.2
|
||||
- trufflehog@3.90.8
|
||||
- trufflehog@3.90.11
|
||||
- yamllint@1.37.1
|
||||
- bandit@1.8.6
|
||||
- trivy@0.66.0
|
||||
- trivy@0.67.2
|
||||
- taplo@0.10.0
|
||||
- ruff@0.13.1
|
||||
- isort@6.0.1
|
||||
- ruff@0.14.1
|
||||
- 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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
||||
|
||||
FROM python:3.13-slim-trixie AS builder
|
||||
FROM python:3.14-slim-trixie AS builder
|
||||
ARG PIO_ENV=native
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Etc/UTC
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# trunk-ignore-all(hadolint/DL3018): Do not pin apk package versions
|
||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
||||
|
||||
FROM python:3.13-alpine3.22 AS builder
|
||||
FROM python:3.14-alpine3.22 AS builder
|
||||
ARG PIO_ENV=native
|
||||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Generate the CI matrix."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import random
|
||||
import re
|
||||
from platformio.project.config import ProjectConfig
|
||||
|
||||
options = sys.argv[1:]
|
||||
parser = argparse.ArgumentParser(description="Generate the CI matrix")
|
||||
parser.add_argument("platform", help="Platform to build for")
|
||||
parser.add_argument(
|
||||
"--level",
|
||||
choices=["extra", "pr"],
|
||||
nargs="*",
|
||||
default=[],
|
||||
help="Board level to build for (omit for full release boards)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
outlist = []
|
||||
|
||||
if len(options) < 1:
|
||||
print(json.dumps(outlist))
|
||||
exit(1)
|
||||
|
||||
cfg = ProjectConfig.get_instance()
|
||||
pio_envs = cfg.envs()
|
||||
|
||||
# Gather all PlatformIO environments for filtering later
|
||||
all_envs = []
|
||||
for pio_env in pio_envs:
|
||||
env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags')
|
||||
env_build_flags = cfg.get(f"env:{pio_env}", "build_flags")
|
||||
env_platform = None
|
||||
for flag in env_build_flags:
|
||||
# Extract the platform from the build flags
|
||||
@@ -37,36 +41,35 @@ for pio_env in pio_envs:
|
||||
exit(1)
|
||||
# Store env details as a dictionary, and add to 'all_envs' list
|
||||
env = {
|
||||
'name': pio_env,
|
||||
'platform': env_platform,
|
||||
'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None),
|
||||
'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False))
|
||||
"ci": {"board": pio_env, "platform": env_platform},
|
||||
"board_level": cfg.get(f"env:{pio_env}", "board_level", default=None),
|
||||
"board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)),
|
||||
}
|
||||
all_envs.append(env)
|
||||
|
||||
# Filter outputs based on options
|
||||
# Check is mutually exclusive with other options (except 'pr')
|
||||
if "check" in options:
|
||||
if "check" in args.platform:
|
||||
for env in all_envs:
|
||||
if env['board_check']:
|
||||
if "pr" in options:
|
||||
if env['board_level'] == 'pr':
|
||||
outlist.append(env['name'])
|
||||
if env["board_check"]:
|
||||
if "pr" in args.level:
|
||||
if env["board_level"] == "pr":
|
||||
outlist.append(env["ci"])
|
||||
else:
|
||||
outlist.append(env['name'])
|
||||
outlist.append(env["ci"])
|
||||
# Filter (non-check) builds by platform
|
||||
else:
|
||||
for env in all_envs:
|
||||
if options[0] == env['platform']:
|
||||
if args.platform == env["ci"]["platform"] or args.platform == "all":
|
||||
# Always include board_level = 'pr'
|
||||
if env['board_level'] == 'pr':
|
||||
outlist.append(env['name'])
|
||||
if env["board_level"] == "pr":
|
||||
outlist.append(env["ci"])
|
||||
# Include board_level = 'extra' when requested
|
||||
elif "extra" in options and env['board_level'] == "extra":
|
||||
outlist.append(env['name'])
|
||||
elif "extra" in args.level and env["board_level"] == "extra":
|
||||
outlist.append(env["ci"])
|
||||
# If no board level is specified, include in release builds (not PR)
|
||||
elif "pr" not in options and not env['board_level']:
|
||||
outlist.append(env['name'])
|
||||
elif "pr" not in args.level and not env["board_level"]:
|
||||
outlist.append(env["ci"])
|
||||
|
||||
# Return as a JSON list
|
||||
print(json.dumps(outlist))
|
||||
|
||||
116
bin/kill-github-actions.sh
Executable file
116
bin/kill-github-actions.sh
Executable file
@@ -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,12 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.7.13" date="2025-10-11">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13</url>
|
||||
</release>
|
||||
<release version="2.7.12" date="2025-10-01">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.12</url>
|
||||
</release>
|
||||
<release version="2.7.11" date="2025-09-24">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.11</url>
|
||||
</release>
|
||||
|
||||
@@ -86,7 +86,7 @@ if platform.name == "espressif32":
|
||||
|
||||
if platform.name == "nordicnrf52":
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex",
|
||||
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2",
|
||||
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py \"$BUILD_DIR/firmware.hex\" -c -f 0xADA52840 -o \"$BUILD_DIR/firmware.uf2\"",
|
||||
"Generating UF2 file"))
|
||||
|
||||
Import("projenv")
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.6.4
|
||||
2.6.7
|
||||
37
boards/heltec_wireless_tracker_v2.json
Normal file
37
boards/heltec_wireless_tracker_v2.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default_8MB.csv"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "heltec_wireless_tracker_v2"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "Heltec Wireless Tracker V2",
|
||||
"upload": {
|
||||
"flash_size": "8MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 8388608,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://heltec.org",
|
||||
"vendor": "Heltec"
|
||||
}
|
||||
52
boards/r1-neo.json
Normal file
52
boards/r1-neo.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x8029"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x239A", "0x802A"]
|
||||
],
|
||||
"usb_product": "Muzi R1 Neo",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "r1-neo",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino", "freertos"],
|
||||
"name": "WisCore RAK4631 Board",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://muzi.works/",
|
||||
"vendor": "Muzi Works"
|
||||
}
|
||||
13
debian/changelog
vendored
13
debian/changelog
vendored
@@ -1,4 +1,10 @@
|
||||
meshtasticd (2.7.11.0) UNRELEASED; urgency=medium
|
||||
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 ]
|
||||
* Initial packaging
|
||||
@@ -7,4 +13,7 @@ meshtasticd (2.7.11.0) UNRELEASED; urgency=medium
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
-- <github-actions[bot]@users.noreply.github.com> Wed, 24 Sep 2025 11:01:13 +0000
|
||||
[ GitHub Actions ]
|
||||
* Version 2.7.12
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 01 Oct 2025 19:51:41 +0000
|
||||
|
||||
@@ -70,7 +70,7 @@ lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
|
||||
https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip
|
||||
# renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master
|
||||
https://github.com/meshtastic/ArduinoThread/archive/7c3ee9e1951551b949763b1f5280f8db1fa4068d.zip
|
||||
https://github.com/meshtastic/ArduinoThread/archive/b841b0415721f1341ea41cccfb4adccfaf951567.zip
|
||||
# renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb
|
||||
nanopb/Nanopb@0.4.91
|
||||
# renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32
|
||||
@@ -120,7 +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/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip
|
||||
https://github.com/meshtastic/device-ui/archive/19b7855e9a1d9deff37391659ca7194e4ef57c43.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
|
||||
@@ -181,6 +181,8 @@ lib_deps =
|
||||
dfrobot/DFRobot_BMM150@1.0.0
|
||||
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
|
||||
adafruit/Adafruit TSL2561@1.1.2
|
||||
# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/BH1750_WE@^1.1.10
|
||||
wollewald/BH1750_WE@^1.1.10
|
||||
|
||||
; (not included in native / portduino)
|
||||
[environmental_extra]
|
||||
|
||||
Submodule protobufs updated: 082bb7cfeb...bf149bbdcc
@@ -11,6 +11,11 @@
|
||||
#include <AudioOutputI2S.h>
|
||||
#include <ESP8266SAM.h>
|
||||
|
||||
#ifdef USE_XL9555
|
||||
#include "ExtensionIOXL9555.hpp"
|
||||
extern ExtensionIOXL9555 io;
|
||||
#endif
|
||||
|
||||
#define AUDIO_THREAD_INTERVAL_MS 100
|
||||
|
||||
class AudioThread : public concurrency::OSThread
|
||||
@@ -20,6 +25,9 @@ class AudioThread : public concurrency::OSThread
|
||||
|
||||
void beginRttl(const void *data, uint32_t len)
|
||||
{
|
||||
#ifdef T_LORA_PAGER
|
||||
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
|
||||
#endif
|
||||
setCPUFast(true);
|
||||
rtttlFile = new AudioFileSourcePROGMEM(data, len);
|
||||
i2sRtttl = new AudioGeneratorRTTTL();
|
||||
@@ -46,6 +54,9 @@ class AudioThread : public concurrency::OSThread
|
||||
rtttlFile = nullptr;
|
||||
|
||||
setCPUFast(false);
|
||||
#ifdef T_LORA_PAGER
|
||||
io.digitalWrite(EXPANDS_AMP_EN, LOW);
|
||||
#endif
|
||||
}
|
||||
|
||||
void readAloud(const char *text)
|
||||
@@ -56,10 +67,16 @@ class AudioThread : public concurrency::OSThread
|
||||
i2sRtttl = nullptr;
|
||||
}
|
||||
|
||||
#ifdef T_LORA_PAGER
|
||||
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
|
||||
#endif
|
||||
ESP8266SAM *sam = new ESP8266SAM;
|
||||
sam->Say(audioOut, text);
|
||||
delete sam;
|
||||
setCPUFast(false);
|
||||
#ifdef T_LORA_PAGER
|
||||
io.digitalWrite(EXPANDS_AMP_EN, LOW);
|
||||
#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -14,16 +14,16 @@ class NodeStatus : public Status
|
||||
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver =
|
||||
CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
|
||||
|
||||
uint8_t numOnline = 0;
|
||||
uint8_t numTotal = 0;
|
||||
uint16_t numOnline = 0;
|
||||
uint16_t numTotal = 0;
|
||||
|
||||
uint8_t lastNumTotal = 0;
|
||||
uint16_t lastNumTotal = 0;
|
||||
|
||||
public:
|
||||
bool forceUpdate = false;
|
||||
|
||||
NodeStatus() { statusType = STATUS_TYPE_NODE; }
|
||||
NodeStatus(uint8_t numOnline, uint8_t numTotal, bool forceUpdate = false) : Status()
|
||||
NodeStatus(uint16_t numOnline, uint16_t numTotal, bool forceUpdate = false) : Status()
|
||||
{
|
||||
this->forceUpdate = forceUpdate;
|
||||
this->numOnline = numOnline;
|
||||
@@ -34,11 +34,11 @@ class NodeStatus : public Status
|
||||
|
||||
void observe(Observable<const NodeStatus *> *source) { statusObserver.observe(source); }
|
||||
|
||||
uint8_t getNumOnline() const { return numOnline; }
|
||||
uint16_t getNumOnline() const { return numOnline; }
|
||||
|
||||
uint8_t getNumTotal() const { return numTotal; }
|
||||
uint16_t getNumTotal() const { return numTotal; }
|
||||
|
||||
uint8_t getLastNumTotal() const { return lastNumTotal; }
|
||||
uint16_t getLastNumTotal() const { return lastNumTotal; }
|
||||
|
||||
bool matches(const NodeStatus *newStatus) const
|
||||
{
|
||||
@@ -56,7 +56,7 @@ class NodeStatus : public Status
|
||||
numTotal = newStatus->getNumTotal();
|
||||
}
|
||||
if (isDirty || newStatus->forceUpdate) {
|
||||
LOG_DEBUG("Node status update: %d online, %d total", numOnline, numTotal);
|
||||
LOG_DEBUG("Node status update: %u online, %u total", numOnline, numTotal);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -562,6 +562,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())
|
||||
@@ -691,6 +692,16 @@ bool Power::setup()
|
||||
#ifdef NRF_APM
|
||||
found = true;
|
||||
#endif
|
||||
#ifdef EXT_PWR_DETECT
|
||||
attachInterrupt(
|
||||
EXT_PWR_DETECT,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
|
||||
enabled = found;
|
||||
low_voltage_counter = 0;
|
||||
@@ -828,8 +839,11 @@ void Power::readPowerStatus()
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
|
||||
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(),
|
||||
powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
|
||||
if (millis() > lastLogTime + 50 * 1000) {
|
||||
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
|
||||
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
|
||||
lastLogTime = millis();
|
||||
}
|
||||
newStatus.notifyObservers(&powerStatus2);
|
||||
#ifdef DEBUG_HEAP
|
||||
if (lastheap != memGet.getFreeHeap()) {
|
||||
|
||||
@@ -86,7 +86,7 @@ int32_t SerialConsole::runOnce()
|
||||
#endif
|
||||
|
||||
int32_t delay = runOncePart();
|
||||
#if defined(SERIAL_HAS_ON_RECEIVE)
|
||||
#if defined(SERIAL_HAS_ON_RECEIVE) || defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
return Port.available() ? delay : INT32_MAX;
|
||||
#elif defined(IS_USB_SERIAL)
|
||||
return HWCDC::isPlugged() ? delay : (1000 * 20);
|
||||
|
||||
@@ -15,7 +15,8 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
|
||||
{
|
||||
// Only provide feedback if buzzer is enabled for notifications
|
||||
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
|
||||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY) {
|
||||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY ||
|
||||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY) {
|
||||
return 0; // Let other handlers process the event
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "pcf8563.h"
|
||||
#endif
|
||||
|
||||
/* Offer chance for variant-specific defines */
|
||||
#include "variant.h"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Version
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -117,6 +120,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define SX126X_MAX_POWER 22
|
||||
#endif
|
||||
|
||||
#ifdef USE_GC1109_PA
|
||||
// Power Amps are often non-linear, so we can use an array of values for the power curve
|
||||
#define NUM_PA_POINTS 22
|
||||
#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
|
||||
#endif
|
||||
|
||||
#ifdef RAK13302
|
||||
#define NUM_PA_POINTS 22
|
||||
#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8
|
||||
#endif
|
||||
|
||||
// Default system gain to 0 if not defined
|
||||
#ifndef TX_GAIN_LORA
|
||||
#define TX_GAIN_LORA 0
|
||||
@@ -254,9 +268,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// convert 24-bit color to 16-bit (56K)
|
||||
#define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
|
||||
|
||||
/* Step #1: offer chance for variant-specific defines */
|
||||
#include "variant.h"
|
||||
|
||||
#if defined(VEXT_ENABLE) && !defined(VEXT_ON_VALUE)
|
||||
// Older variant.h files might not be defining this value, so stay with the old default
|
||||
#define VEXT_ON_VALUE LOW
|
||||
|
||||
@@ -25,8 +25,8 @@ ScanI2C::FoundDevice ScanI2C::firstScreen() const
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstRTC() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563};
|
||||
return firstOfOrNONE(2, types);
|
||||
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563, RTC_RX8130CE};
|
||||
return firstOfOrNONE(3, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
||||
|
||||
@@ -14,6 +14,7 @@ class ScanI2C
|
||||
SCREEN_ST7567,
|
||||
RTC_RV3028,
|
||||
RTC_PCF8563,
|
||||
RTC_RX8130CE,
|
||||
CARDKB,
|
||||
TDECKKB,
|
||||
BBQ10KB,
|
||||
@@ -81,7 +82,8 @@ class ScanI2C
|
||||
BHI260AP,
|
||||
BMM150,
|
||||
TSL2561,
|
||||
DRV2605
|
||||
DRV2605,
|
||||
BH1750
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
16
src/detect/ScanI2CConsumer.cpp
Normal file
16
src/detect/ScanI2CConsumer.cpp
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
13
src/detect/ScanI2CConsumer.h
Normal file
13
src/detect/ScanI2CConsumer.h
Normal file
@@ -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);
|
||||
@@ -106,6 +106,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
|
||||
if (i2cBus->available())
|
||||
i2cBus->read();
|
||||
}
|
||||
LOG_DEBUG("Register value: 0x%x", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -197,6 +198,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
#ifdef PCF8563_RTC
|
||||
SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563", (uint8_t)addr.address)
|
||||
#endif
|
||||
#ifdef RX8130CE_RTC
|
||||
SCAN_SIMPLE_CASE(RX8130CE_RTC, RTC_RX8130CE, "RX8130CE", (uint8_t)addr.address)
|
||||
#endif
|
||||
|
||||
case CARDKB_ADDR:
|
||||
// Do we have the RAK14006 instead?
|
||||
@@ -374,13 +378,13 @@ 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 == 0xe9c || registerValue == 0xc8d) {
|
||||
type = SHT4X;
|
||||
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
||||
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2);
|
||||
if (registerValue == 0x5449) {
|
||||
type = OPT3001;
|
||||
logFoundDevice("OPT3001", (uint8_t)addr.address);
|
||||
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2) != 0) { // unique SHT4x serial number
|
||||
type = SHT4X;
|
||||
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
||||
} else {
|
||||
type = SHT31;
|
||||
logFoundDevice("SHT31", (uint8_t)addr.address);
|
||||
@@ -481,7 +485,25 @@ 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);
|
||||
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);
|
||||
@@ -577,7 +599,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;
|
||||
|
||||
|
||||
460
src/gps/GPS.cpp
460
src/gps/GPS.cpp
@@ -494,22 +494,10 @@ bool GPS::setup()
|
||||
if (!didSerialInit) {
|
||||
int msglen = 0;
|
||||
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
#ifdef TRACKER_T1000_E
|
||||
// add power up/down strategy, improve ag3335 detection success
|
||||
digitalWrite(PIN_GPS_EN, LOW);
|
||||
delay(500);
|
||||
digitalWrite(GPS_VRTC_EN, LOW);
|
||||
delay(1000);
|
||||
digitalWrite(GPS_VRTC_EN, HIGH);
|
||||
delay(500);
|
||||
digitalWrite(PIN_GPS_EN, HIGH);
|
||||
delay(1000);
|
||||
#endif
|
||||
if (probeTries < GPS_PROBETRIES) {
|
||||
LOG_DEBUG("Probe for GPS at %d", serialSpeeds[speedSelect]);
|
||||
gnssModel = probe(serialSpeeds[speedSelect]);
|
||||
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
if (++speedSelect == array_count(serialSpeeds)) {
|
||||
if (currentStep == 0 && ++speedSelect == array_count(serialSpeeds)) {
|
||||
speedSelect = 0;
|
||||
++probeTries;
|
||||
}
|
||||
@@ -518,10 +506,9 @@ bool GPS::setup()
|
||||
// Rare Serial Speeds
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
if (probeTries == GPS_PROBETRIES) {
|
||||
LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]);
|
||||
gnssModel = probe(rareSerialSpeeds[speedSelect]);
|
||||
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
if (++speedSelect == array_count(rareSerialSpeeds)) {
|
||||
if (currentStep == 0 && ++speedSelect == array_count(rareSerialSpeeds)) {
|
||||
LOG_WARN("Give up on GPS probe and set to %d", GPS_BAUDRATE);
|
||||
return true;
|
||||
}
|
||||
@@ -1033,7 +1020,7 @@ void GPS::down()
|
||||
LOG_DEBUG("%us until next search", sleepTime / 1000);
|
||||
|
||||
// If update interval less than 10 seconds, no attempt to sleep
|
||||
if (updateInterval <= 10 * 1000UL || sleepTime == 0)
|
||||
if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS || sleepTime == 0)
|
||||
setPowerState(GPS_IDLE);
|
||||
|
||||
else {
|
||||
@@ -1094,7 +1081,7 @@ int32_t GPS::runOnce()
|
||||
return disable();
|
||||
}
|
||||
if (!setup())
|
||||
return 2000; // Setup failed, re-run in two seconds
|
||||
return currentDelay; // Setup failed, re-run in two seconds
|
||||
|
||||
// We have now loaded our saved preferences from flash
|
||||
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||
@@ -1104,6 +1091,29 @@ int32_t GPS::runOnce()
|
||||
publishUpdate();
|
||||
}
|
||||
|
||||
// ======================== GPS_ACTIVE state ========================
|
||||
// In GPS_ACTIVE state, GPS is powered on and we're receiving NMEA messages.
|
||||
// We use the following logic to determine when to update the local position
|
||||
// or time by running GPS::publishUpdate.
|
||||
// Note: Local position update is asynchronous to position broadcast. We
|
||||
// generally run this state every gps_update_interval seconds, and in most cases
|
||||
// gps_update_interval is faster than the position broadcast interval so there's a
|
||||
// fresh position ready when the device wants to broadcast one on the mesh.
|
||||
//
|
||||
// 1. Got a time for the first time --> set the time, don't publish.
|
||||
// 2. Got a lock for the first time
|
||||
// --> If gps_update_interval is <= 10s --> publishUpdate
|
||||
// --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
|
||||
// 3. Got a lock after turning back on
|
||||
// --> If gps_update_interval is <= 10s --> publishUpdate
|
||||
// --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
|
||||
// 4. Hold has expired
|
||||
// --> If we have a time and a location --> publishUpdate
|
||||
// --> down()
|
||||
// 5. Search time has expired
|
||||
// --> If we have a time and a location --> publishUpdate
|
||||
// --> If we had a location before but don't now --> publishUpdate
|
||||
// --> down()
|
||||
if (whileActive()) {
|
||||
// if we have received valid NMEA claim we are connected
|
||||
setConnected();
|
||||
@@ -1113,55 +1123,81 @@ int32_t GPS::runOnce()
|
||||
if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue())
|
||||
up();
|
||||
|
||||
// If we've already set time from the GPS, no need to ask the GPS
|
||||
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
||||
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
|
||||
gotTime = true;
|
||||
shouldPublish = true;
|
||||
}
|
||||
|
||||
// quality of the previous fix. We set it to 0 when we go down, so it's a way
|
||||
// to check if we're getting a lock after being GPS_OFF.
|
||||
uint8_t prev_fixQual = fixQual;
|
||||
bool gotLoc = lookForLocation();
|
||||
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
|
||||
LOG_DEBUG("hasValidLocation RISING EDGE");
|
||||
hasValidLocation = true;
|
||||
shouldPublish = true;
|
||||
// Hold for 20secs after getting a lock to download ephemeris etc
|
||||
fixHoldEnds = millis() + 20000;
|
||||
}
|
||||
|
||||
if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on.
|
||||
fixHoldEnds = millis() + 20000;
|
||||
shouldPublish = true; // Publish immediately, since next publish is at end of hold
|
||||
}
|
||||
if (powerState == GPS_ACTIVE) {
|
||||
// if gps_update_interval is <=10s, GPS never goes off, so we treat that differently
|
||||
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
|
||||
|
||||
bool tooLong = scheduling.searchedTooLong();
|
||||
if (tooLong)
|
||||
LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
|
||||
// 1. Got a time for the first time
|
||||
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
|
||||
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
|
||||
gotTime = true;
|
||||
}
|
||||
|
||||
// Once we get a location we no longer desperately want an update
|
||||
if ((gotLoc && gotTime) || tooLong) {
|
||||
// 2. Got a lock for the first time, or 3. Got a lock after turning back on
|
||||
bool gotLoc = lookForLocation();
|
||||
if (gotLoc) {
|
||||
#ifdef GPS_DEBUG
|
||||
if (!hasValidLocation) { // declare that we have location ASAP
|
||||
LOG_DEBUG("hasValidLocation RISING EDGE");
|
||||
}
|
||||
#endif
|
||||
if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS) {
|
||||
hasValidLocation = true;
|
||||
shouldPublish = true;
|
||||
} else if (!hasValidLocation || prev_fixQual == 0 || (fixHoldEnds + GPS_THREAD_INTERVAL) < millis()) {
|
||||
hasValidLocation = true;
|
||||
// Hold for up to 20secs after getting a lock to download ephemeris etc
|
||||
uint32_t holdTime = updateInterval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS;
|
||||
if (holdTime > GPS_FIX_HOLD_MAX_MS)
|
||||
holdTime = GPS_FIX_HOLD_MAX_MS;
|
||||
fixHoldEnds = millis() + holdTime;
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG("Holding for %ums after lock", holdTime);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool tooLong = scheduling.searchedTooLong();
|
||||
if (tooLong && !gotLoc) {
|
||||
LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
|
||||
// we didn't get a location during this ack window, therefore declare loss of lock
|
||||
if (hasValidLocation) {
|
||||
LOG_DEBUG("hasValidLocation FALLING EDGE");
|
||||
}
|
||||
p = meshtastic_Position_init_default;
|
||||
hasValidLocation = false;
|
||||
}
|
||||
if (millis() > fixHoldEnds) {
|
||||
shouldPublish = true; // publish our update at the end of the lock hold
|
||||
publishUpdate();
|
||||
down();
|
||||
p = meshtastic_Position_init_default;
|
||||
hasValidLocation = false;
|
||||
shouldPublish = true;
|
||||
#ifdef GPS_DEBUG
|
||||
} else {
|
||||
LOG_DEBUG("hasValidLocation FALLING EDGE");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Hold has expired , Search time has expired, we got a time only, or we never needed to hold.
|
||||
bool holdExpired = (fixHoldEnds != 0 && millis() > fixHoldEnds);
|
||||
if (shouldPublish || tooLong || holdExpired) {
|
||||
if (gotTime && hasValidLocation) {
|
||||
shouldPublish = true;
|
||||
}
|
||||
if (shouldPublish) {
|
||||
fixHoldEnds = 0;
|
||||
publishUpdate();
|
||||
}
|
||||
|
||||
// There's a chance we just got a time, so keep going to see if we can get a location too
|
||||
if (tooLong || holdExpired) {
|
||||
down();
|
||||
}
|
||||
|
||||
#ifdef GPS_DEBUG
|
||||
} else if (fixHoldEnds != 0) {
|
||||
LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// If state has changed do a publish
|
||||
publishUpdate();
|
||||
// ===================== end GPS_ACTIVE state ========================
|
||||
|
||||
if (config.position.fixed_position == true && hasValidLocation)
|
||||
return disable(); // This should trigger when we have a fixed position, and get that first position
|
||||
@@ -1218,163 +1254,197 @@ static const char *DETECTED_MESSAGE = "%s detected";
|
||||
|
||||
GnssModel_t GPS::probe(int serialSpeed)
|
||||
{
|
||||
uint8_t buffer[768] = {0};
|
||||
|
||||
switch (currentStep) {
|
||||
case 0: {
|
||||
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
|
||||
_serial_gps->end();
|
||||
_serial_gps->begin(serialSpeed);
|
||||
_serial_gps->end();
|
||||
_serial_gps->begin(serialSpeed);
|
||||
#elif defined(ARCH_RP2040)
|
||||
_serial_gps->end();
|
||||
_serial_gps->setFIFOSize(256);
|
||||
_serial_gps->begin(serialSpeed);
|
||||
_serial_gps->end();
|
||||
_serial_gps->setFIFOSize(256);
|
||||
_serial_gps->begin(serialSpeed);
|
||||
#else
|
||||
if (_serial_gps->baudRate() != serialSpeed) {
|
||||
LOG_DEBUG("Set Baud to %i", serialSpeed);
|
||||
_serial_gps->updateBaudRate(serialSpeed);
|
||||
}
|
||||
if (_serial_gps->baudRate() != serialSpeed) {
|
||||
LOG_DEBUG("Set GPS Baud to %i", serialSpeed);
|
||||
_serial_gps->updateBaudRate(serialSpeed);
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(&ublox_info, 0, sizeof(ublox_info));
|
||||
uint8_t buffer[768] = {0};
|
||||
delay(100);
|
||||
memset(&ublox_info, 0, sizeof(ublox_info));
|
||||
delay(100);
|
||||
|
||||
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
|
||||
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
|
||||
delay(20);
|
||||
// Close NMEA sequences on Ublox
|
||||
_serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
|
||||
_serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
|
||||
_serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
|
||||
delay(20);
|
||||
// Close NMEA sequences on CM121
|
||||
_serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n");
|
||||
_serial_gps->write("$CFGMSG,0,2,0,1*18\r\n");
|
||||
_serial_gps->write("$CFGMSG,0,3,0,1*19\r\n");
|
||||
delay(20);
|
||||
|
||||
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
|
||||
std::vector<ChipInfo> unicore = {
|
||||
{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
|
||||
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
|
||||
|
||||
std::vector<ChipInfo> atgm = {
|
||||
{"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
|
||||
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
|
||||
{"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
|
||||
PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
|
||||
|
||||
/* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
|
||||
_serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
|
||||
_serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
|
||||
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration
|
||||
std::vector<ChipInfo> airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
|
||||
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
|
||||
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
|
||||
PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
|
||||
|
||||
PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
|
||||
PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
|
||||
|
||||
// Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
|
||||
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
|
||||
delay(20);
|
||||
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
|
||||
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
|
||||
{"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B},
|
||||
{"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
|
||||
|
||||
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
|
||||
|
||||
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
|
||||
UBXChecksum(cfg_rate, sizeof(cfg_rate));
|
||||
clearBuffer();
|
||||
_serial_gps->write(cfg_rate, sizeof(cfg_rate));
|
||||
// Check that the returned response class and message ID are correct
|
||||
GPS_RESPONSE response = getACK(0x06, 0x08, 750);
|
||||
if (response == GNSS_RESPONSE_NONE) {
|
||||
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
|
||||
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
|
||||
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
|
||||
delay(20);
|
||||
// Close NMEA sequences on Ublox
|
||||
_serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
|
||||
_serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
|
||||
_serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
|
||||
delay(20);
|
||||
// Close NMEA sequences on CM121
|
||||
_serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n");
|
||||
_serial_gps->write("$CFGMSG,0,2,0,1*18\r\n");
|
||||
_serial_gps->write("$CFGMSG,0,3,0,1*19\r\n");
|
||||
currentDelay = 20;
|
||||
currentStep = 1;
|
||||
return GNSS_MODEL_UNKNOWN;
|
||||
} else if (response == GNSS_RESPONSE_FRAME_ERRORS) {
|
||||
LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed);
|
||||
}
|
||||
case 1: {
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
uint8_t _message_MONVER[8] = {
|
||||
0xB5, 0x62, // Sync message for UBX protocol
|
||||
0x0A, 0x04, // Message class and ID (UBX-MON-VER)
|
||||
0x00, 0x00, // Length of payload (we're asking for an answer, so no payload)
|
||||
0x00, 0x00 // Checksum
|
||||
};
|
||||
// Get Ublox gnss module hardware and software info
|
||||
UBXChecksum(_message_MONVER, sizeof(_message_MONVER));
|
||||
clearBuffer();
|
||||
_serial_gps->write(_message_MONVER, sizeof(_message_MONVER));
|
||||
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
|
||||
std::vector<ChipInfo> unicore = {
|
||||
{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
|
||||
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
|
||||
currentDelay = 20;
|
||||
currentStep = 2;
|
||||
return GNSS_MODEL_UNKNOWN;
|
||||
}
|
||||
case 2: {
|
||||
std::vector<ChipInfo> atgm = {
|
||||
{"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
|
||||
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
|
||||
{"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
|
||||
PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
|
||||
currentDelay = 20;
|
||||
currentStep = 3;
|
||||
return GNSS_MODEL_UNKNOWN;
|
||||
}
|
||||
case 3: {
|
||||
/* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
|
||||
_serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
|
||||
_serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
|
||||
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration
|
||||
std::vector<ChipInfo> airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
|
||||
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
|
||||
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
|
||||
PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
|
||||
currentDelay = 20;
|
||||
currentStep = 4;
|
||||
return GNSS_MODEL_UNKNOWN;
|
||||
}
|
||||
case 4: {
|
||||
PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
|
||||
PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
|
||||
currentDelay = 20;
|
||||
currentStep = 5;
|
||||
return GNSS_MODEL_UNKNOWN;
|
||||
}
|
||||
case 5: {
|
||||
|
||||
uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200);
|
||||
if (len) {
|
||||
uint16_t position = 0;
|
||||
for (int i = 0; i < 30; i++) {
|
||||
ublox_info.swVersion[i] = buffer[position];
|
||||
position++;
|
||||
}
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ublox_info.hwVersion[i] = buffer[position];
|
||||
position++;
|
||||
}
|
||||
// Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
|
||||
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
|
||||
delay(20);
|
||||
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
|
||||
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
|
||||
{"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B},
|
||||
{"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
|
||||
|
||||
while (len >= position + 30) {
|
||||
for (int i = 0; i < 30; i++) {
|
||||
ublox_info.extension[ublox_info.extensionNo][i] = buffer[position];
|
||||
position++;
|
||||
}
|
||||
ublox_info.extensionNo++;
|
||||
if (ublox_info.extensionNo > 9)
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Module Info : ");
|
||||
LOG_DEBUG("Soft version: %s", ublox_info.swVersion);
|
||||
LOG_DEBUG("Hard version: %s", ublox_info.hwVersion);
|
||||
LOG_DEBUG("Extensions:%d", ublox_info.extensionNo);
|
||||
for (int i = 0; i < ublox_info.extensionNo; i++) {
|
||||
LOG_DEBUG(" %s", ublox_info.extension[i]);
|
||||
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
|
||||
currentDelay = 20;
|
||||
currentStep = 6;
|
||||
return GNSS_MODEL_UNKNOWN;
|
||||
}
|
||||
case 6: {
|
||||
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
|
||||
UBXChecksum(cfg_rate, sizeof(cfg_rate));
|
||||
clearBuffer();
|
||||
_serial_gps->write(cfg_rate, sizeof(cfg_rate));
|
||||
// Check that the returned response class and message ID are correct
|
||||
GPS_RESPONSE response = getACK(0x06, 0x08, 750);
|
||||
if (response == GNSS_RESPONSE_NONE) {
|
||||
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
|
||||
currentDelay = 2000;
|
||||
currentStep = 0;
|
||||
return GNSS_MODEL_UNKNOWN;
|
||||
} else if (response == GNSS_RESPONSE_FRAME_ERRORS) {
|
||||
LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed);
|
||||
}
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
uint8_t _message_MONVER[8] = {
|
||||
0xB5, 0x62, // Sync message for UBX protocol
|
||||
0x0A, 0x04, // Message class and ID (UBX-MON-VER)
|
||||
0x00, 0x00, // Length of payload (we're asking for an answer, so no payload)
|
||||
0x00, 0x00 // Checksum
|
||||
};
|
||||
// Get Ublox gnss module hardware and software info
|
||||
UBXChecksum(_message_MONVER, sizeof(_message_MONVER));
|
||||
clearBuffer();
|
||||
_serial_gps->write(_message_MONVER, sizeof(_message_MONVER));
|
||||
|
||||
// tips: extensionNo field is 0 on some 6M GNSS modules
|
||||
for (int i = 0; i < ublox_info.extensionNo; ++i) {
|
||||
if (!strncmp(ublox_info.extension[i], "MOD=", 4)) {
|
||||
strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer));
|
||||
} else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) {
|
||||
char *ptr = nullptr;
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer));
|
||||
LOG_DEBUG("Protocol Version:%s", (char *)buffer);
|
||||
if (strlen((char *)buffer)) {
|
||||
ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10);
|
||||
LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version);
|
||||
} else {
|
||||
ublox_info.protocol_version = 0;
|
||||
uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200);
|
||||
if (len) {
|
||||
uint16_t position = 0;
|
||||
for (int i = 0; i < 30; i++) {
|
||||
ublox_info.swVersion[i] = buffer[position];
|
||||
position++;
|
||||
}
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ublox_info.hwVersion[i] = buffer[position];
|
||||
position++;
|
||||
}
|
||||
|
||||
while (len >= position + 30) {
|
||||
for (int i = 0; i < 30; i++) {
|
||||
ublox_info.extension[ublox_info.extensionNo][i] = buffer[position];
|
||||
position++;
|
||||
}
|
||||
ublox_info.extensionNo++;
|
||||
if (ublox_info.extensionNo > 9)
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Module Info : ");
|
||||
LOG_DEBUG("Soft version: %s", ublox_info.swVersion);
|
||||
LOG_DEBUG("Hard version: %s", ublox_info.hwVersion);
|
||||
LOG_DEBUG("Extensions:%d", ublox_info.extensionNo);
|
||||
for (int i = 0; i < ublox_info.extensionNo; i++) {
|
||||
LOG_DEBUG(" %s", ublox_info.extension[i]);
|
||||
}
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
// tips: extensionNo field is 0 on some 6M GNSS modules
|
||||
for (int i = 0; i < ublox_info.extensionNo; ++i) {
|
||||
if (!strncmp(ublox_info.extension[i], "MOD=", 4)) {
|
||||
strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer));
|
||||
} else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) {
|
||||
char *ptr = nullptr;
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer));
|
||||
LOG_DEBUG("Protocol Version:%s", (char *)buffer);
|
||||
if (strlen((char *)buffer)) {
|
||||
ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10);
|
||||
LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version);
|
||||
} else {
|
||||
ublox_info.protocol_version = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) {
|
||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6");
|
||||
return GNSS_MODEL_UBLOX6;
|
||||
} else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) {
|
||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7");
|
||||
return GNSS_MODEL_UBLOX7;
|
||||
} else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) {
|
||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8");
|
||||
return GNSS_MODEL_UBLOX8;
|
||||
} else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) {
|
||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9");
|
||||
return GNSS_MODEL_UBLOX9;
|
||||
} else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) {
|
||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10");
|
||||
return GNSS_MODEL_UBLOX10;
|
||||
if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) {
|
||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6");
|
||||
return GNSS_MODEL_UBLOX6;
|
||||
} else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) {
|
||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7");
|
||||
return GNSS_MODEL_UBLOX7;
|
||||
} else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) {
|
||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8");
|
||||
return GNSS_MODEL_UBLOX8;
|
||||
} else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) {
|
||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9");
|
||||
return GNSS_MODEL_UBLOX9;
|
||||
} else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) {
|
||||
LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10");
|
||||
return GNSS_MODEL_UBLOX10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
|
||||
currentDelay = 2000;
|
||||
currentStep = 0;
|
||||
return GNSS_MODEL_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -1589,8 +1659,12 @@ bool GPS::lookForLocation()
|
||||
|
||||
#ifndef TINYGPS_OPTION_NO_STATISTICS
|
||||
if (reader.failedChecksum() > lastChecksumFailCount) {
|
||||
LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
|
||||
reader.failedChecksum());
|
||||
// In a GPS_DEBUG build we want to log all of these. In production, we only care if there are many of them.
|
||||
#ifndef GPS_DEBUG
|
||||
if (reader.failedChecksum() > 4)
|
||||
#endif
|
||||
LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
|
||||
reader.failedChecksum());
|
||||
lastChecksumFailCount = reader.failedChecksum();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
#define GPS_EN_ACTIVE 1
|
||||
#endif
|
||||
|
||||
static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL;
|
||||
static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000;
|
||||
|
||||
typedef enum {
|
||||
GNSS_MODEL_ATGM336H,
|
||||
GNSS_MODEL_MTK,
|
||||
@@ -151,6 +154,8 @@ class GPS : private concurrency::OSThread
|
||||
TinyGPSPlus reader;
|
||||
uint8_t fixQual = 0; // fix quality from GPGGA
|
||||
uint32_t lastChecksumFailCount = 0;
|
||||
uint8_t currentStep = 0;
|
||||
int32_t currentDelay = 2000;
|
||||
|
||||
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
|
||||
// (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field
|
||||
@@ -173,8 +178,6 @@ class GPS : private concurrency::OSThread
|
||||
*/
|
||||
bool hasValidLocation = false; // default to false, until we complete our first read
|
||||
|
||||
bool isInPowersave = false;
|
||||
|
||||
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
|
||||
|
||||
bool hasGPS = false; // Do we have a GPS we are talking to
|
||||
|
||||
@@ -109,6 +109,35 @@ RTCSetResult readFromRTC()
|
||||
}
|
||||
return RTCSetResultSuccess;
|
||||
}
|
||||
#elif defined(RX8130CE_RTC)
|
||||
if (rtc_found.address == RX8130CE_RTC) {
|
||||
uint32_t now = millis();
|
||||
ArtronShop_RX8130CE rtc(&Wire);
|
||||
tm t;
|
||||
if (rtc.getTime(&t)) {
|
||||
tv.tv_sec = gm_mktime(&t);
|
||||
tv.tv_usec = 0;
|
||||
|
||||
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||
LOG_DEBUG("Read RTC time from RX8130CE getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900,
|
||||
t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
||||
#ifdef BUILD_EPOCH
|
||||
if (tv.tv_sec < BUILD_EPOCH) {
|
||||
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
|
||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||
lastTimeValidationWarning = millis();
|
||||
}
|
||||
return RTCSetResultInvalidTime;
|
||||
}
|
||||
#endif
|
||||
if (currentQuality == RTCQualityNone) {
|
||||
timeStartMsec = now;
|
||||
zeroOffsetSecs = tv.tv_sec;
|
||||
currentQuality = RTCQualityDevice;
|
||||
}
|
||||
return RTCSetResultSuccess;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (!gettimeofday(&tv, NULL)) {
|
||||
uint32_t now = millis();
|
||||
@@ -214,6 +243,17 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
|
||||
LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
|
||||
t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
|
||||
}
|
||||
#elif defined(RX8130CE_RTC)
|
||||
if (rtc_found.address == RX8130CE_RTC) {
|
||||
ArtronShop_RX8130CE rtc(&Wire);
|
||||
tm *t = gmtime(&tv->tv_sec);
|
||||
if (rtc.setTime(*t)) {
|
||||
LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1,
|
||||
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
|
||||
} else {
|
||||
LOG_WARN("Failed to set time for RX8130CE");
|
||||
}
|
||||
}
|
||||
#elif defined(ARCH_ESP32)
|
||||
settimeofday(tv, NULL);
|
||||
#endif
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
#include "sys/time.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef RX8130CE_RTC
|
||||
#include <ArtronShop_RX8130CE.h>
|
||||
#endif
|
||||
|
||||
enum RTCQuality {
|
||||
|
||||
/// We haven't had our RTC set yet
|
||||
|
||||
687
src/graphics/Panel_sdl.cpp
Normal file
687
src/graphics/Panel_sdl.cpp
Normal file
@@ -0,0 +1,687 @@
|
||||
/*----------------------------------------------------------------------------/
|
||||
Lovyan GFX - Graphics library for embedded devices.
|
||||
|
||||
Original Source:
|
||||
https://github.com/lovyan03/LovyanGFX/
|
||||
|
||||
Licence:
|
||||
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
|
||||
|
||||
Author:
|
||||
[lovyan03](https://twitter.com/lovyan03)
|
||||
|
||||
Contributors:
|
||||
[ciniml](https://github.com/ciniml)
|
||||
[mongonta0716](https://github.com/mongonta0716)
|
||||
[tobozo](https://github.com/tobozo)
|
||||
|
||||
Porting for SDL:
|
||||
[imliubo](https://github.com/imliubo)
|
||||
/----------------------------------------------------------------------------*/
|
||||
#include "Panel_sdl.hpp"
|
||||
|
||||
#if defined(SDL_h_)
|
||||
|
||||
// #include "../common.hpp"
|
||||
// #include "../../misc/common_function.hpp"
|
||||
// #include "../../Bus.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
namespace lgfx
|
||||
{
|
||||
inline namespace v1
|
||||
{
|
||||
SDL_Keymod Panel_sdl::_keymod = KMOD_NONE;
|
||||
static SDL_semaphore *_update_in_semaphore = nullptr;
|
||||
static SDL_semaphore *_update_out_semaphore = nullptr;
|
||||
volatile static uint32_t _in_step_exec = 0;
|
||||
volatile static uint32_t _msec_step_exec = 512;
|
||||
static bool _inited = false;
|
||||
static bool _all_close = false;
|
||||
|
||||
volatile uint8_t Panel_sdl::_gpio_dummy_values[EMULATED_GPIO_MAX];
|
||||
|
||||
static inline void *heap_alloc_dma(size_t length)
|
||||
{
|
||||
return malloc(length);
|
||||
} // aligned_alloc(16, length);
|
||||
static inline void heap_free(void *buf)
|
||||
{
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static std::list<monitor_t *> _list_monitor;
|
||||
|
||||
static monitor_t *const getMonitorByWindowID(uint32_t windowID)
|
||||
{
|
||||
for (auto &m : _list_monitor) {
|
||||
if (SDL_GetWindowID(m->window) == windowID) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
static std::vector<Panel_sdl::KeyCodeMapping_t> _key_code_map;
|
||||
|
||||
void Panel_sdl::addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio)
|
||||
{
|
||||
if (gpio > EMULATED_GPIO_MAX)
|
||||
return;
|
||||
KeyCodeMapping_t map;
|
||||
map.keycode = keyCode;
|
||||
map.gpio = gpio;
|
||||
_key_code_map.push_back(map);
|
||||
}
|
||||
|
||||
int Panel_sdl::getKeyCodeMapping(SDL_KeyCode keyCode)
|
||||
{
|
||||
for (const auto &i : _key_code_map) {
|
||||
if (i.keycode == keyCode)
|
||||
return i.gpio;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Panel_sdl::_event_proc(void)
|
||||
{
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) {
|
||||
auto mon = getMonitorByWindowID(event.button.windowID);
|
||||
int gpio = -1;
|
||||
|
||||
/// Check key mapping
|
||||
gpio = getKeyCodeMapping((SDL_KeyCode)event.key.keysym.sym);
|
||||
if (gpio < 0) {
|
||||
switch (event.key.keysym.sym) { /// M5StackのBtnA~BtnCのエミュレート;
|
||||
// case SDLK_LEFT: gpio = 39; break;
|
||||
// case SDLK_DOWN: gpio = 38; break;
|
||||
// case SDLK_RIGHT: gpio = 37; break;
|
||||
// case SDLK_UP: gpio = 36; break;
|
||||
|
||||
/// L/Rキーで画面回転
|
||||
case SDLK_r:
|
||||
case SDLK_l:
|
||||
if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) {
|
||||
if (mon != nullptr) {
|
||||
mon->frame_rotation = (mon->frame_rotation += event.key.keysym.sym == SDLK_r ? 1 : -1);
|
||||
int x, y, w, h;
|
||||
SDL_GetWindowSize(mon->window, &w, &h);
|
||||
SDL_GetWindowPosition(mon->window, &x, &y);
|
||||
SDL_SetWindowSize(mon->window, h, w);
|
||||
SDL_SetWindowPosition(mon->window, x + (w - h) / 2, y + (h - w) / 2);
|
||||
mon->panel->sdl_invalidate();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/// 1~6キーで画面拡大率変更
|
||||
case SDLK_1:
|
||||
case SDLK_2:
|
||||
case SDLK_3:
|
||||
case SDLK_4:
|
||||
case SDLK_5:
|
||||
case SDLK_6:
|
||||
if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) {
|
||||
if (mon != nullptr) {
|
||||
int size = 1 + (event.key.keysym.sym - SDLK_1);
|
||||
_update_scaling(mon, size, size);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type == SDL_KEYDOWN) {
|
||||
Panel_sdl::gpio_lo(gpio);
|
||||
} else {
|
||||
Panel_sdl::gpio_hi(gpio);
|
||||
}
|
||||
} else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION) {
|
||||
auto mon = getMonitorByWindowID(event.button.windowID);
|
||||
if (mon != nullptr) {
|
||||
{
|
||||
int x, y, w, h;
|
||||
SDL_GetWindowSize(mon->window, &w, &h);
|
||||
SDL_GetMouseState(&x, &y);
|
||||
float sf = sinf(mon->frame_angle * M_PI / 180);
|
||||
float cf = cosf(mon->frame_angle * M_PI / 180);
|
||||
x -= w / 2.0f;
|
||||
y -= h / 2.0f;
|
||||
float nx = y * sf + x * cf;
|
||||
float ny = y * cf - x * sf;
|
||||
if (mon->frame_rotation & 1) {
|
||||
std::swap(w, h);
|
||||
}
|
||||
x = (nx * mon->frame_width / w) + (mon->frame_width >> 1);
|
||||
y = (ny * mon->frame_height / h) + (mon->frame_height >> 1);
|
||||
mon->touch_x = x - mon->frame_inner_x;
|
||||
mon->touch_y = y - mon->frame_inner_y;
|
||||
}
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) {
|
||||
mon->touched = true;
|
||||
}
|
||||
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) {
|
||||
mon->touched = false;
|
||||
}
|
||||
}
|
||||
} else if (event.type == SDL_WINDOWEVENT) {
|
||||
auto monitor = getMonitorByWindowID(event.window.windowID);
|
||||
if (monitor) {
|
||||
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
int mw, mh;
|
||||
SDL_GetRendererOutputSize(monitor->renderer, &mw, &mh);
|
||||
if (monitor->frame_rotation & 1) {
|
||||
std::swap(mw, mh);
|
||||
}
|
||||
monitor->scaling_x = (mw * 2 / monitor->frame_width) / 2.0f;
|
||||
monitor->scaling_y = (mh * 2 / monitor->frame_height) / 2.0f;
|
||||
monitor->panel->sdl_invalidate();
|
||||
} else if (event.window.event == SDL_WINDOWEVENT_CLOSE) {
|
||||
monitor->closing = true;
|
||||
}
|
||||
}
|
||||
} else if (event.type == SDL_QUIT) {
|
||||
for (auto &m : _list_monitor) {
|
||||
m->closing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// デバッガでステップ実行されていることを検出するスレッド用関数。
|
||||
static int detectDebugger(bool *running)
|
||||
{
|
||||
uint32_t prev_ms = SDL_GetTicks();
|
||||
do {
|
||||
SDL_Delay(1);
|
||||
uint32_t ms = SDL_GetTicks();
|
||||
/// 時間間隔が広すぎる場合はステップ実行中 (ブレークポイントで止まった)と判断する。
|
||||
/// また、解除されたと判断した後も1023msecほど状態を維持する。
|
||||
if (ms - prev_ms > 64) {
|
||||
_in_step_exec = _msec_step_exec;
|
||||
} else if (_in_step_exec) {
|
||||
--_in_step_exec;
|
||||
}
|
||||
prev_ms = ms;
|
||||
} while (*running);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Panel_sdl::_update_proc(void)
|
||||
{
|
||||
for (auto it = _list_monitor.begin(); it != _list_monitor.end();) {
|
||||
if ((*it)->closing) {
|
||||
if ((*it)->texture_frameimage) {
|
||||
SDL_DestroyTexture((*it)->texture_frameimage);
|
||||
}
|
||||
SDL_DestroyTexture((*it)->texture);
|
||||
SDL_DestroyRenderer((*it)->renderer);
|
||||
SDL_DestroyWindow((*it)->window);
|
||||
_list_monitor.erase(it++);
|
||||
if (_list_monitor.empty()) {
|
||||
_all_close = true;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
(*it)->panel->sdl_update();
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
int Panel_sdl::setup(void)
|
||||
{
|
||||
if (_inited)
|
||||
return 1;
|
||||
_inited = true;
|
||||
|
||||
/// Add default keycode mapping
|
||||
/// M5StackのBtnA~BtnCのエミュレート;
|
||||
addKeyCodeMapping(SDLK_LEFT, 39);
|
||||
addKeyCodeMapping(SDLK_DOWN, 38);
|
||||
addKeyCodeMapping(SDLK_RIGHT, 37);
|
||||
addKeyCodeMapping(SDLK_UP, 36);
|
||||
|
||||
SDL_CreateThread((SDL_ThreadFunction)detectDebugger, "dbg", &_inited);
|
||||
|
||||
_update_in_semaphore = SDL_CreateSemaphore(0);
|
||||
_update_out_semaphore = SDL_CreateSemaphore(0);
|
||||
for (size_t pin = 0; pin < EMULATED_GPIO_MAX; ++pin) {
|
||||
gpio_hi(pin);
|
||||
}
|
||||
/*Initialize the SDL*/
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
SDL_StartTextInput();
|
||||
|
||||
// SDL_SetThreadPriority(SDL_ThreadPriority::SDL_THREAD_PRIORITY_HIGH);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Panel_sdl::loop(void)
|
||||
{
|
||||
if (!_inited)
|
||||
return 1;
|
||||
|
||||
_event_proc();
|
||||
SDL_SemWaitTimeout(_update_in_semaphore, 1);
|
||||
_update_proc();
|
||||
_event_proc();
|
||||
if (SDL_SemValue(_update_out_semaphore) == 0) {
|
||||
SDL_SemPost(_update_out_semaphore);
|
||||
}
|
||||
|
||||
return _all_close;
|
||||
}
|
||||
|
||||
int Panel_sdl::close(void)
|
||||
{
|
||||
if (!_inited)
|
||||
return 1;
|
||||
_inited = false;
|
||||
|
||||
SDL_StopTextInput();
|
||||
SDL_DestroySemaphore(_update_in_semaphore);
|
||||
SDL_DestroySemaphore(_update_out_semaphore);
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Panel_sdl::main(int (*fn)(bool *), uint32_t msec_step_exec)
|
||||
{
|
||||
_msec_step_exec = msec_step_exec;
|
||||
|
||||
/// SDLの準備
|
||||
if (0 != Panel_sdl::setup()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// ユーザコード関数の動作・停止フラグ
|
||||
bool running = true;
|
||||
|
||||
/// ユーザコード関数を起動する
|
||||
auto thread = SDL_CreateThread((SDL_ThreadFunction)fn, "fn", &running);
|
||||
|
||||
/// 全部のウィンドウが閉じられるまでSDLのイベント・描画処理を継続
|
||||
while (0 == Panel_sdl::loop()) {
|
||||
};
|
||||
|
||||
/// ユーザコード関数を終了する
|
||||
running = false;
|
||||
SDL_WaitThread(thread, nullptr);
|
||||
|
||||
/// SDLを終了する
|
||||
return Panel_sdl::close();
|
||||
}
|
||||
|
||||
void Panel_sdl::setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y)
|
||||
{
|
||||
monitor.scaling_x = scaling_x;
|
||||
monitor.scaling_y = scaling_y;
|
||||
}
|
||||
|
||||
void Panel_sdl::setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y)
|
||||
{
|
||||
monitor.frame_image = frame_image;
|
||||
monitor.frame_width = frame_width;
|
||||
monitor.frame_height = frame_height;
|
||||
monitor.frame_inner_x = inner_x;
|
||||
monitor.frame_inner_y = inner_y;
|
||||
}
|
||||
|
||||
void Panel_sdl::setFrameRotation(uint_fast16_t frame_rotation)
|
||||
{
|
||||
monitor.frame_rotation = frame_rotation;
|
||||
monitor.frame_angle = (monitor.frame_rotation) * 90;
|
||||
}
|
||||
|
||||
Panel_sdl::~Panel_sdl(void)
|
||||
{
|
||||
_list_monitor.remove(&monitor);
|
||||
SDL_DestroyMutex(_sdl_mutex);
|
||||
}
|
||||
|
||||
Panel_sdl::Panel_sdl(void) : Panel_FrameBufferBase()
|
||||
{
|
||||
_sdl_mutex = SDL_CreateMutex();
|
||||
_auto_display = true;
|
||||
monitor.panel = this;
|
||||
}
|
||||
|
||||
bool Panel_sdl::init(bool use_reset)
|
||||
{
|
||||
initFrameBuffer(_cfg.panel_width * 4, _cfg.panel_height);
|
||||
bool res = Panel_FrameBufferBase::init(use_reset);
|
||||
|
||||
_list_monitor.push_back(&monitor);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
color_depth_t Panel_sdl::setColorDepth(color_depth_t depth)
|
||||
{
|
||||
auto bits = depth & color_depth_t::bit_mask;
|
||||
if (bits >= 16) {
|
||||
depth = (bits > 16) ? rgb888_3Byte : rgb565_2Byte;
|
||||
} else {
|
||||
depth = (depth == color_depth_t::grayscale_8bit) ? grayscale_8bit : rgb332_1Byte;
|
||||
}
|
||||
_write_depth = depth;
|
||||
_read_depth = depth;
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
Panel_sdl::lock_t::lock_t(Panel_sdl *parent) : _parent{parent}
|
||||
{
|
||||
SDL_LockMutex(parent->_sdl_mutex);
|
||||
};
|
||||
|
||||
Panel_sdl::lock_t::~lock_t(void)
|
||||
{
|
||||
++_parent->_modified_counter;
|
||||
SDL_UnlockMutex(_parent->_sdl_mutex);
|
||||
if (SDL_SemValue(_update_in_semaphore) < 2) {
|
||||
SDL_SemPost(_update_in_semaphore);
|
||||
if (!_in_step_exec) {
|
||||
SDL_SemWaitTimeout(_update_out_semaphore, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void Panel_sdl::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor)
|
||||
{
|
||||
lock_t lock(this);
|
||||
Panel_FrameBufferBase::drawPixelPreclipped(x, y, rawcolor);
|
||||
}
|
||||
|
||||
void Panel_sdl::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor)
|
||||
{
|
||||
lock_t lock(this);
|
||||
Panel_FrameBufferBase::writeFillRectPreclipped(x, y, w, h, rawcolor);
|
||||
}
|
||||
|
||||
void Panel_sdl::writeBlock(uint32_t rawcolor, uint32_t length)
|
||||
{
|
||||
// lock_t lock(this);
|
||||
Panel_FrameBufferBase::writeBlock(rawcolor, length);
|
||||
}
|
||||
|
||||
void Panel_sdl::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param, bool use_dma)
|
||||
{
|
||||
lock_t lock(this);
|
||||
Panel_FrameBufferBase::writeImage(x, y, w, h, param, use_dma);
|
||||
}
|
||||
|
||||
void Panel_sdl::writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param)
|
||||
{
|
||||
lock_t lock(this);
|
||||
Panel_FrameBufferBase::writeImageARGB(x, y, w, h, param);
|
||||
}
|
||||
|
||||
void Panel_sdl::writePixels(pixelcopy_t *param, uint32_t len, bool use_dma)
|
||||
{
|
||||
lock_t lock(this);
|
||||
Panel_FrameBufferBase::writePixels(param, len, use_dma);
|
||||
}
|
||||
|
||||
void Panel_sdl::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h)
|
||||
{
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)w;
|
||||
(void)h;
|
||||
if (_in_step_exec) {
|
||||
if (_display_counter != _modified_counter) {
|
||||
do {
|
||||
SDL_SemPost(_update_in_semaphore);
|
||||
SDL_SemWaitTimeout(_update_out_semaphore, 1);
|
||||
} while (_display_counter != _modified_counter);
|
||||
SDL_Delay(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint_fast8_t Panel_sdl::getTouchRaw(touch_point_t *tp, uint_fast8_t count)
|
||||
{
|
||||
(void)count;
|
||||
tp->x = monitor.touch_x;
|
||||
tp->y = monitor.touch_y;
|
||||
tp->size = monitor.touched ? 1 : 0;
|
||||
tp->id = 0;
|
||||
return monitor.touched;
|
||||
}
|
||||
|
||||
void Panel_sdl::setWindowTitle(const char *title)
|
||||
{
|
||||
_window_title = title;
|
||||
if (monitor.window) {
|
||||
SDL_SetWindowTitle(monitor.window, _window_title);
|
||||
}
|
||||
}
|
||||
|
||||
void Panel_sdl::_update_scaling(monitor_t *mon, float sx, float sy)
|
||||
{
|
||||
mon->scaling_x = sx;
|
||||
mon->scaling_y = sy;
|
||||
int nw = mon->frame_width;
|
||||
int nh = mon->frame_height;
|
||||
if (mon->frame_rotation & 1) {
|
||||
std::swap(nw, nh);
|
||||
}
|
||||
|
||||
int x, y, w, h;
|
||||
int rw, rh;
|
||||
SDL_GetRendererOutputSize(mon->renderer, &rw, &rh);
|
||||
SDL_GetWindowSize(mon->window, &w, &h);
|
||||
nw = nw * sx * w / rw;
|
||||
nh = nh * sy * h / rh;
|
||||
SDL_GetWindowPosition(mon->window, &x, &y);
|
||||
SDL_SetWindowSize(mon->window, nw, nh);
|
||||
SDL_SetWindowPosition(mon->window, x + (w - nw) / 2, y + (h - nh) / 2);
|
||||
mon->panel->sdl_invalidate();
|
||||
}
|
||||
|
||||
void Panel_sdl::sdl_create(monitor_t *m)
|
||||
{
|
||||
int flag = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
#if SDL_FULLSCREEN
|
||||
flag |= SDL_WINDOW_FULLSCREEN;
|
||||
#endif
|
||||
|
||||
if (m->frame_width < _cfg.panel_width) {
|
||||
m->frame_width = _cfg.panel_width;
|
||||
}
|
||||
if (m->frame_height < _cfg.panel_height) {
|
||||
m->frame_height = _cfg.panel_height;
|
||||
}
|
||||
|
||||
int window_width = m->frame_width * m->scaling_x;
|
||||
int window_height = m->frame_height * m->scaling_y;
|
||||
int scaling_x = m->scaling_x;
|
||||
int scaling_y = m->scaling_y;
|
||||
if (m->frame_rotation & 1) {
|
||||
std::swap(window_width, window_height);
|
||||
std::swap(scaling_x, scaling_y);
|
||||
}
|
||||
|
||||
{
|
||||
m->window = SDL_CreateWindow(_window_title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height,
|
||||
flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/
|
||||
}
|
||||
m->renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
m->texture =
|
||||
SDL_CreateTexture(m->renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, _cfg.panel_width, _cfg.panel_height);
|
||||
SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_NONE);
|
||||
|
||||
if (m->frame_image) {
|
||||
// 枠画像用のサーフェイスを作成
|
||||
auto sf = SDL_CreateRGBSurfaceFrom((void *)m->frame_image, m->frame_width, m->frame_height, 32, m->frame_width * 4,
|
||||
0xFF000000, 0xFF0000, 0xFF00, 0xFF);
|
||||
if (sf != nullptr) {
|
||||
// 枠画像からテクスチャを作成
|
||||
m->texture_frameimage = SDL_CreateTextureFromSurface(m->renderer, sf);
|
||||
SDL_FreeSurface(sf);
|
||||
}
|
||||
}
|
||||
SDL_SetTextureBlendMode(m->texture_frameimage, SDL_BLENDMODE_BLEND);
|
||||
_update_scaling(m, scaling_x, scaling_y);
|
||||
}
|
||||
|
||||
void Panel_sdl::sdl_update(void)
|
||||
{
|
||||
if (monitor.renderer == nullptr) {
|
||||
sdl_create(&monitor);
|
||||
}
|
||||
|
||||
bool step_exec = _in_step_exec;
|
||||
|
||||
if (_texupdate_counter != _modified_counter) {
|
||||
pixelcopy_t pc(nullptr, color_depth_t::rgb888_3Byte, _write_depth, false);
|
||||
if (_write_depth == rgb565_2Byte) {
|
||||
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, swap565_t>;
|
||||
} else if (_write_depth == rgb888_3Byte) {
|
||||
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, bgr888_t>;
|
||||
} else if (_write_depth == rgb332_1Byte) {
|
||||
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, rgb332_t>;
|
||||
} else if (_write_depth == grayscale_8bit) {
|
||||
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, grayscale_t>;
|
||||
}
|
||||
|
||||
if (0 == SDL_LockMutex(_sdl_mutex)) {
|
||||
_texupdate_counter = _modified_counter;
|
||||
for (int y = 0; y < _cfg.panel_height; ++y) {
|
||||
pc.src_x32 = 0;
|
||||
pc.src_data = _lines_buffer[y];
|
||||
pc.fp_copy(&_texturebuf[y * _cfg.panel_width], 0, _cfg.panel_width, &pc);
|
||||
}
|
||||
SDL_UnlockMutex(_sdl_mutex);
|
||||
SDL_UpdateTexture(monitor.texture, nullptr, _texturebuf, _cfg.panel_width * sizeof(rgb888_t));
|
||||
}
|
||||
}
|
||||
|
||||
int angle = monitor.frame_angle;
|
||||
int target = (monitor.frame_rotation) * 90;
|
||||
angle = (((target * 4) + (angle * 4) + (angle < target ? 8 : 0)) >> 3);
|
||||
|
||||
if (monitor.frame_angle != angle) { // 表示する向きを変える
|
||||
monitor.frame_angle = angle;
|
||||
sdl_invalidate();
|
||||
} else if (monitor.frame_rotation & ~3u) {
|
||||
monitor.frame_rotation &= 3;
|
||||
monitor.frame_angle = (monitor.frame_rotation) * 90;
|
||||
sdl_invalidate();
|
||||
}
|
||||
|
||||
if (_invalidated || (_display_counter != _texupdate_counter)) {
|
||||
SDL_RendererInfo info;
|
||||
if (0 == SDL_GetRendererInfo(monitor.renderer, &info)) {
|
||||
// ステップ実行中はVSYNCを待機しない
|
||||
if (((bool)(info.flags & SDL_RENDERER_PRESENTVSYNC)) == step_exec) {
|
||||
SDL_RenderSetVSync(monitor.renderer, !step_exec);
|
||||
}
|
||||
}
|
||||
{
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
#if defined(M5GFX_BACK_COLOR)
|
||||
red = ((M5GFX_BACK_COLOR) >> 16) & 0xFF;
|
||||
green = ((M5GFX_BACK_COLOR) >> 8) & 0xFF;
|
||||
blue = ((M5GFX_BACK_COLOR)) & 0xFF;
|
||||
#endif
|
||||
SDL_SetRenderDrawColor(monitor.renderer, red, green, blue, 0xFF);
|
||||
}
|
||||
SDL_RenderClear(monitor.renderer);
|
||||
if (_invalidated) {
|
||||
_invalidated = false;
|
||||
int mw, mh;
|
||||
SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh);
|
||||
}
|
||||
render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height, angle);
|
||||
render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle);
|
||||
SDL_RenderPresent(monitor.renderer);
|
||||
_display_counter = _texupdate_counter;
|
||||
if (_invalidated) {
|
||||
_invalidated = false;
|
||||
SDL_SetRenderDrawColor(monitor.renderer, 0, 0, 0, 0xFF);
|
||||
SDL_RenderClear(monitor.renderer);
|
||||
render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height,
|
||||
angle);
|
||||
render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle);
|
||||
SDL_RenderPresent(monitor.renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Panel_sdl::render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle)
|
||||
{
|
||||
SDL_Point pivot;
|
||||
pivot.x = (monitor.frame_width / 2.0f - tx) * (float)monitor.scaling_x;
|
||||
pivot.y = (monitor.frame_height / 2.0f - ty) * (float)monitor.scaling_y;
|
||||
SDL_Rect dstrect;
|
||||
dstrect.w = tw * monitor.scaling_x;
|
||||
dstrect.h = th * monitor.scaling_y;
|
||||
int mw, mh;
|
||||
SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh);
|
||||
dstrect.x = mw / 2.0f - pivot.x;
|
||||
dstrect.y = mh / 2.0f - pivot.y;
|
||||
SDL_RenderCopyEx(monitor.renderer, texture, nullptr, &dstrect, angle, &pivot, SDL_RendererFlip::SDL_FLIP_NONE);
|
||||
}
|
||||
|
||||
bool Panel_sdl::initFrameBuffer(size_t width, size_t height)
|
||||
{
|
||||
uint8_t **lineArray = (uint8_t **)heap_alloc_dma(height * sizeof(uint8_t *));
|
||||
if (nullptr == lineArray) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_texturebuf = (rgb888_t *)heap_alloc_dma(width * height * sizeof(rgb888_t));
|
||||
|
||||
/// 8byte alignment;
|
||||
width = (width + 7) & ~7u;
|
||||
|
||||
_lines_buffer = lineArray;
|
||||
memset(lineArray, 0, height * sizeof(uint8_t *));
|
||||
|
||||
uint8_t *framebuffer = (uint8_t *)heap_alloc_dma(width * height + 16);
|
||||
|
||||
auto fb = framebuffer;
|
||||
{
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
lineArray[y] = fb;
|
||||
fb += width;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Panel_sdl::deinitFrameBuffer(void)
|
||||
{
|
||||
auto lines = _lines_buffer;
|
||||
_lines_buffer = nullptr;
|
||||
if (lines != nullptr) {
|
||||
heap_free(lines[0]);
|
||||
heap_free(lines);
|
||||
}
|
||||
if (_texturebuf) {
|
||||
heap_free(_texturebuf);
|
||||
_texturebuf = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
} // namespace v1
|
||||
} // namespace lgfx
|
||||
|
||||
#endif
|
||||
166
src/graphics/Panel_sdl.hpp
Normal file
166
src/graphics/Panel_sdl.hpp
Normal file
@@ -0,0 +1,166 @@
|
||||
/*----------------------------------------------------------------------------/
|
||||
Lovyan GFX - Graphics library for embedded devices.
|
||||
|
||||
Original Source:
|
||||
https://github.com/lovyan03/LovyanGFX/
|
||||
|
||||
Licence:
|
||||
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
|
||||
|
||||
Author:
|
||||
[lovyan03](https://twitter.com/lovyan03)
|
||||
|
||||
Contributors:
|
||||
[ciniml](https://github.com/ciniml)
|
||||
[mongonta0716](https://github.com/mongonta0716)
|
||||
[tobozo](https://github.com/tobozo)
|
||||
|
||||
Porting for SDL:
|
||||
[imliubo](https://github.com/imliubo)
|
||||
/----------------------------------------------------------------------------*/
|
||||
#pragma once
|
||||
|
||||
#define SDL_MAIN_HANDLED
|
||||
// cppcheck-suppress preprocessorErrorDirective
|
||||
#if __has_include(<SDL2/SDL.h>)
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_main.h>
|
||||
#elif __has_include(<SDL.h>)
|
||||
#include <SDL.h>
|
||||
#include <SDL_main.h>
|
||||
#endif
|
||||
|
||||
#if defined(SDL_h_)
|
||||
#include "lgfx/v1/Touch.hpp"
|
||||
#include "lgfx/v1/misc/range.hpp"
|
||||
#include "lgfx/v1/panel/Panel_FrameBufferBase.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace lgfx
|
||||
{
|
||||
inline namespace v1
|
||||
{
|
||||
|
||||
struct Panel_sdl;
|
||||
struct monitor_t {
|
||||
SDL_Window *window = nullptr;
|
||||
SDL_Renderer *renderer = nullptr;
|
||||
SDL_Texture *texture = nullptr;
|
||||
SDL_Texture *texture_frameimage = nullptr;
|
||||
Panel_sdl *panel = nullptr;
|
||||
|
||||
// 外枠
|
||||
const void *frame_image = 0;
|
||||
uint_fast16_t frame_width = 0;
|
||||
uint_fast16_t frame_height = 0;
|
||||
uint_fast16_t frame_inner_x = 0;
|
||||
uint_fast16_t frame_inner_y = 0;
|
||||
int_fast16_t frame_rotation = 0;
|
||||
int_fast16_t frame_angle = 0;
|
||||
|
||||
float scaling_x = 1;
|
||||
float scaling_y = 1;
|
||||
int_fast16_t touch_x, touch_y;
|
||||
bool touched = false;
|
||||
bool closing = false;
|
||||
};
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
struct Touch_sdl : public ITouch {
|
||||
bool init(void) override { return true; }
|
||||
void wakeup(void) override {}
|
||||
void sleep(void) override {}
|
||||
bool isEnable(void) override { return true; };
|
||||
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override { return 0; }
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
struct Panel_sdl : public Panel_FrameBufferBase {
|
||||
static constexpr size_t EMULATED_GPIO_MAX = 128;
|
||||
static volatile uint8_t _gpio_dummy_values[EMULATED_GPIO_MAX];
|
||||
|
||||
public:
|
||||
Panel_sdl(void);
|
||||
virtual ~Panel_sdl(void);
|
||||
|
||||
bool init(bool use_reset) override;
|
||||
|
||||
color_depth_t setColorDepth(color_depth_t depth) override;
|
||||
|
||||
void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override;
|
||||
|
||||
// void setInvert(bool invert) override {}
|
||||
void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override;
|
||||
void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override;
|
||||
void writeBlock(uint32_t rawcolor, uint32_t length) override;
|
||||
void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param,
|
||||
bool use_dma) override;
|
||||
void writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param) override;
|
||||
void writePixels(pixelcopy_t *param, uint32_t len, bool use_dma) override;
|
||||
|
||||
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override;
|
||||
|
||||
void setWindowTitle(const char *title);
|
||||
void setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y);
|
||||
void setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y);
|
||||
void setFrameRotation(uint_fast16_t frame_rotaion);
|
||||
void setBrightness(uint8_t brightness) override{};
|
||||
|
||||
static volatile void gpio_hi(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 1; }
|
||||
static volatile void gpio_lo(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 0; }
|
||||
static volatile bool gpio_in(uint32_t pin) { return _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)]; }
|
||||
|
||||
static int setup(void);
|
||||
static int loop(void);
|
||||
static int close(void);
|
||||
|
||||
static int main(int (*fn)(bool *), uint32_t msec_step_exec = 512);
|
||||
|
||||
static void setShortcutKeymod(SDL_Keymod keymod) { _keymod = keymod; }
|
||||
|
||||
struct KeyCodeMapping_t {
|
||||
SDL_KeyCode keycode = SDLK_UNKNOWN;
|
||||
uint8_t gpio = 0;
|
||||
};
|
||||
static void addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio);
|
||||
static int getKeyCodeMapping(SDL_KeyCode keyCode);
|
||||
|
||||
protected:
|
||||
const char *_window_title = "LGFX Simulator";
|
||||
SDL_mutex *_sdl_mutex = nullptr;
|
||||
|
||||
void sdl_create(monitor_t *m);
|
||||
void sdl_update(void);
|
||||
|
||||
touch_point_t _touch_point;
|
||||
monitor_t monitor;
|
||||
|
||||
rgb888_t *_texturebuf = nullptr;
|
||||
uint_fast16_t _modified_counter;
|
||||
uint_fast16_t _texupdate_counter;
|
||||
uint_fast16_t _display_counter;
|
||||
bool _invalidated;
|
||||
|
||||
static void _event_proc(void);
|
||||
static void _update_proc(void);
|
||||
static void _update_scaling(monitor_t *m, float sx, float sy);
|
||||
void sdl_invalidate(void) { _invalidated = true; }
|
||||
void render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle);
|
||||
bool initFrameBuffer(size_t width, size_t height);
|
||||
void deinitFrameBuffer(void);
|
||||
|
||||
static SDL_Keymod _keymod;
|
||||
|
||||
struct lock_t {
|
||||
lock_t(Panel_sdl *parent);
|
||||
~lock_t();
|
||||
|
||||
protected:
|
||||
Panel_sdl *_parent;
|
||||
};
|
||||
};
|
||||
//----------------------------------------------------------------------------
|
||||
} // namespace v1
|
||||
} // namespace lgfx
|
||||
#endif
|
||||
@@ -100,7 +100,7 @@ namespace graphics
|
||||
#define NUM_EXTRA_FRAMES 3 // text message and debug frame
|
||||
// if defined a pixel will blink to show redraws
|
||||
// #define SHOW_REDRAWS
|
||||
|
||||
#define ASCII_BELL '\x07'
|
||||
// A text message frame + debug frame + all the node infos
|
||||
FrameCallback *normalFrames;
|
||||
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
||||
@@ -443,7 +443,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
if (uiconfig.screen_brightness == 1)
|
||||
digitalWrite(PIN_EINK_EN, HIGH);
|
||||
#elif defined(PCA_PIN_EINK_EN)
|
||||
if (uiconfig.screen_brightness == 1)
|
||||
if (uiconfig.screen_brightness > 0)
|
||||
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
||||
#endif
|
||||
|
||||
@@ -453,7 +453,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
#endif
|
||||
|
||||
dispdev->displayOn();
|
||||
#ifdef HELTEC_TRACKER_V1_X
|
||||
#if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2)
|
||||
ui->init();
|
||||
#endif
|
||||
#ifdef USE_ST7789
|
||||
@@ -1428,6 +1428,9 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
}
|
||||
nodeDB->updateGUI = false;
|
||||
break;
|
||||
case STATUS_TYPE_POWER:
|
||||
forceDisplay(true);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1458,28 +1461,36 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
||||
}
|
||||
// === Prepare banner content ===
|
||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
|
||||
const meshtastic_Channel channel =
|
||||
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
|
||||
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
|
||||
|
||||
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
|
||||
|
||||
char banner[256];
|
||||
|
||||
// Check for bell character in message to determine alert type
|
||||
bool isAlert = false;
|
||||
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
|
||||
if (msgRaw[i] == '\x07') {
|
||||
isAlert = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_bell_vibra ||
|
||||
moduleConfig.external_notification.alert_bell_buzzer)
|
||||
// Check for bell character to determine if this message is an alert
|
||||
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
|
||||
if (msgRaw[i] == ASCII_BELL) {
|
||||
isAlert = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Unlike generic messages, alerts (when enabled via the ext notif module) ignore any
|
||||
// 'mute' preferences set to any specific node or channel.
|
||||
if (isAlert) {
|
||||
if (longName && longName[0]) {
|
||||
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
|
||||
} else {
|
||||
strcpy(banner, "Alert Received");
|
||||
}
|
||||
} else {
|
||||
screen->showSimpleBanner(banner, 3000);
|
||||
} 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");
|
||||
@@ -1490,14 +1501,21 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
||||
} else {
|
||||
strcpy(banner, "New Message");
|
||||
}
|
||||
}
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
screen->setOn(true);
|
||||
screen->showSimpleBanner(banner, 1500);
|
||||
playLongBeep();
|
||||
screen->setOn(true);
|
||||
screen->showSimpleBanner(banner, 1500);
|
||||
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
|
||||
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
|
||||
(!isBroadcast(packet->to) && isToUs(packet))) {
|
||||
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
|
||||
// - packet contains an alert and alert bell buzzer is enabled
|
||||
// - packet is a non-broadcast that is addressed to this node
|
||||
playLongBeep();
|
||||
}
|
||||
#else
|
||||
screen->showSimpleBanner(banner, 3000);
|
||||
screen->showSimpleBanner(banner, 3000);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "RTC.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "graphics/draw/UIRenderer.h"
|
||||
#include "main.h"
|
||||
#include "meshtastic/config.pb.h"
|
||||
@@ -423,3 +425,4 @@ std::string sanitizeString(const std::string &input)
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
#endif
|
||||
@@ -751,10 +751,8 @@ static LGFX *tft = nullptr;
|
||||
|
||||
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
|
||||
#elif ARCH_PORTDUINO
|
||||
#include "Panel_sdl.hpp"
|
||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||||
#if defined(LGFX_SDL)
|
||||
#include <lgfx/v1/platforms/sdl/Panel_sdl.hpp>
|
||||
#endif
|
||||
|
||||
class LGFX : public lgfx::LGFX_Device
|
||||
{
|
||||
@@ -783,10 +781,10 @@ class LGFX : public lgfx::LGFX_Device
|
||||
_panel_instance = new lgfx::Panel_ILI9488;
|
||||
else if (portduino_config.displayPanel == hx8357d)
|
||||
_panel_instance = new lgfx::Panel_HX8357D;
|
||||
#if defined(LGFX_SDL)
|
||||
else if (portduino_config.displayPanel == x11) {
|
||||
#if defined(SDL_h_)
|
||||
|
||||
else if (portduino_config.displayPanel == x11)
|
||||
_panel_instance = new lgfx::Panel_sdl;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
_panel_instance = new lgfx::Panel_NULL;
|
||||
@@ -799,8 +797,9 @@ class LGFX : public lgfx::LGFX_Device
|
||||
|
||||
buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable)
|
||||
|
||||
_bus_instance.config(buscfg); // applies the set value to the bus.
|
||||
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
|
||||
_bus_instance.config(buscfg); // applies the set value to the bus.
|
||||
if (portduino_config.displayPanel != x11)
|
||||
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
|
||||
|
||||
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
|
||||
LOG_DEBUG("Width: %d, Height: %d", portduino_config.displayWidth, portduino_config.displayHeight);
|
||||
@@ -848,7 +847,7 @@ class LGFX : public lgfx::LGFX_Device
|
||||
_touch_instance->config(touch_cfg);
|
||||
_panel_instance->setTouch(_touch_instance);
|
||||
}
|
||||
#if defined(LGFX_SDL)
|
||||
#if defined(SDL_h_)
|
||||
if (portduino_config.displayPanel == x11) {
|
||||
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
|
||||
sdl_panel_->setup();
|
||||
@@ -1237,7 +1236,7 @@ void TFTDisplay::display(bool fromBlank)
|
||||
|
||||
void TFTDisplay::sdlLoop()
|
||||
{
|
||||
#if defined(LGFX_SDL)
|
||||
#if defined(SDL_h_)
|
||||
static int lastPressed = 0;
|
||||
static int shuttingDown = false;
|
||||
if (portduino_config.displayPanel == x11) {
|
||||
@@ -1247,27 +1246,26 @@ void TFTDisplay::sdlLoop()
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
}
|
||||
|
||||
// debounce
|
||||
if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed))
|
||||
if (lastPressed != 0 && !sdl_panel_->gpio_in(lastPressed))
|
||||
return;
|
||||
if (!lgfx::v1::gpio_in(37)) {
|
||||
if (!sdl_panel_->gpio_in(37)) {
|
||||
lastPressed = 37;
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
} else if (!lgfx::v1::gpio_in(36)) {
|
||||
} else if (!sdl_panel_->gpio_in(36)) {
|
||||
lastPressed = 36;
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
} else if (!lgfx::v1::gpio_in(38)) {
|
||||
} else if (!sdl_panel_->gpio_in(38)) {
|
||||
lastPressed = 38;
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
} else if (!lgfx::v1::gpio_in(39)) {
|
||||
} else if (!sdl_panel_->gpio_in(39)) {
|
||||
lastPressed = 39;
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
} else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) {
|
||||
} else if (!sdl_panel_->gpio_in(SDL_SCANCODE_KP_ENTER)) {
|
||||
lastPressed = SDL_SCANCODE_KP_ENTER;
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -116,6 +116,8 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) {
|
||||
config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected);
|
||||
auto changes = SEGMENT_CONFIG;
|
||||
|
||||
// This is needed as we wait til picking the LoRa region to generate keys for the first time.
|
||||
if (!owner.is_licensed) {
|
||||
bool keygenSuccess = false;
|
||||
@@ -124,6 +126,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
||||
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
|
||||
keygenSuccess = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
LOG_INFO("Generate new PKI keys");
|
||||
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
|
||||
@@ -141,7 +144,14 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
||||
if (myRegion->dutyCycle < 100) {
|
||||
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
|
||||
}
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
|
||||
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
|
||||
// Default broker is in use, so subscribe to the appropriate MQTT root topic for this region
|
||||
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
|
||||
changes |= SEGMENT_MODULECONFIG;
|
||||
}
|
||||
|
||||
service->reloadConfig(changes);
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
}
|
||||
};
|
||||
@@ -505,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 {
|
||||
@@ -564,7 +574,7 @@ void menuHandler::textMessageBaseMenu()
|
||||
|
||||
void menuHandler::systemBaseMenu()
|
||||
{
|
||||
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd };
|
||||
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
|
||||
static const char *optionsArray[enumEnd] = {"Back"};
|
||||
static int optionsEnumArray[enumEnd] = {Back};
|
||||
int options = 1;
|
||||
@@ -573,12 +583,10 @@ void menuHandler::systemBaseMenu()
|
||||
optionsEnumArray[options++] = Notifications;
|
||||
#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \
|
||||
defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
|
||||
optionsArray[options] = "Screen Options";
|
||||
optionsArray[options] = "Display Options";
|
||||
optionsEnumArray[options++] = ScreenOptions;
|
||||
#endif
|
||||
|
||||
optionsArray[options] = "Frame Visiblity Toggle";
|
||||
optionsEnumArray[options++] = FrameToggles;
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
optionsArray[options] = "Bluetooth";
|
||||
#else
|
||||
@@ -616,9 +624,6 @@ void menuHandler::systemBaseMenu()
|
||||
} else if (selected == PowerMenu) {
|
||||
menuHandler::menuQueue = menuHandler::power_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == FrameToggles) {
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
} else if (selected == Test) {
|
||||
menuHandler::menuQueue = menuHandler::test_menu;
|
||||
screen->runNow();
|
||||
@@ -752,6 +757,32 @@ 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"};
|
||||
@@ -911,11 +942,11 @@ void menuHandler::BluetoothToggleMenu()
|
||||
|
||||
void menuHandler::BuzzerModeMenu()
|
||||
{
|
||||
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"};
|
||||
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only", "DMs Only"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Buzzer Mode";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 4;
|
||||
bannerOptions.optionsCount = 5;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
config.device.buzzer_mode = (meshtastic_Config_DeviceConfig_BuzzerMode)selected;
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
@@ -1294,11 +1325,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, FrameToggles, DisplayUnits };
|
||||
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";
|
||||
@@ -1311,8 +1347,14 @@ void menuHandler::screenOptionsMenu()
|
||||
optionsEnumArray[options++] = ScreenColor;
|
||||
#endif
|
||||
|
||||
optionsArray[options] = "Frame Visiblity Toggle";
|
||||
optionsEnumArray[options++] = FrameToggles;
|
||||
|
||||
optionsArray[options] = "Display Units";
|
||||
optionsEnumArray[options++] = DisplayUnits;
|
||||
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Screen Options";
|
||||
bannerOptions.message = "Display Options";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = options;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
@@ -1323,6 +1365,15 @@ 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 if (selected == FrameToggles) {
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
} else if (selected == DisplayUnits) {
|
||||
menuHandler::menuQueue = menuHandler::DisplayUnits;
|
||||
screen->runNow();
|
||||
} else {
|
||||
menuQueue = system_base_menu;
|
||||
screen->runNow();
|
||||
@@ -1421,6 +1472,8 @@ void menuHandler::FrameToggles_menu()
|
||||
lora,
|
||||
clock,
|
||||
show_favorites,
|
||||
show_telemetry,
|
||||
show_power,
|
||||
enumEnd
|
||||
};
|
||||
static const char *optionsArray[enumEnd] = {"Finish"};
|
||||
@@ -1459,6 +1512,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;
|
||||
@@ -1513,6 +1572,42 @@ 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);
|
||||
}
|
||||
|
||||
void menuHandler::DisplayUnits_menu()
|
||||
{
|
||||
enum optionsNumbers { Back, MetricUnits, ImperialUnits };
|
||||
|
||||
static const char *optionsArray[] = {"Back", "Metric", "Imperial"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = " Select display units";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 3;
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
|
||||
bannerOptions.InitialSelected = 2;
|
||||
else
|
||||
bannerOptions.InitialSelected = 1;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == MetricUnits) {
|
||||
config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_METRIC;
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
} else if (selected == ImperialUnits) {
|
||||
config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL;
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
} else {
|
||||
menuHandler::menuQueue = menuHandler::screen_options_menu;
|
||||
screen->runNow();
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
@@ -1584,6 +1679,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case brightness_picker:
|
||||
BrightnessPickerMenu();
|
||||
break;
|
||||
case node_name_length_menu:
|
||||
nodeNameLengthMenu();
|
||||
break;
|
||||
case reboot_menu:
|
||||
rebootMenu();
|
||||
break;
|
||||
@@ -1629,6 +1727,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case FrameToggles:
|
||||
FrameToggles_menu();
|
||||
break;
|
||||
case DisplayUnits:
|
||||
DisplayUnits_menu();
|
||||
break;
|
||||
case throttle_message:
|
||||
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
|
||||
break;
|
||||
|
||||
@@ -43,7 +43,9 @@ class menuHandler
|
||||
key_verification_final_prompt,
|
||||
trace_route_menu,
|
||||
throttle_message,
|
||||
FrameToggles
|
||||
node_name_length_menu,
|
||||
FrameToggles,
|
||||
DisplayUnits
|
||||
};
|
||||
static screenMenus menuQueue;
|
||||
|
||||
@@ -85,7 +87,9 @@ class menuHandler
|
||||
static void notificationsMenu();
|
||||
static void screenOptionsMenu();
|
||||
static void powerMenu();
|
||||
static void nodeNameLengthMenu();
|
||||
static void FrameToggles_menu();
|
||||
static void DisplayUnits_menu();
|
||||
static void textMessageMenu();
|
||||
|
||||
private:
|
||||
|
||||
@@ -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
|
||||
@@ -397,6 +425,12 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
{
|
||||
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
|
||||
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
|
||||
bool locationScreen = false;
|
||||
|
||||
if (strcmp(title, "Bearings") == 0)
|
||||
locationScreen = true;
|
||||
else if (strcmp(title, "Distance") == 0)
|
||||
locationScreen = true;
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
int columnWidth = display->getWidth();
|
||||
#else
|
||||
@@ -412,7 +446,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
|
||||
int totalEntries = nodeDB->getNumMeshNodes();
|
||||
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
|
||||
|
||||
int numskipped = 0;
|
||||
int visibleNodeRows = totalRowsAvailable;
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
int totalColumns = 1;
|
||||
@@ -432,6 +466,10 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
int rowCount = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i) {
|
||||
if (locationScreen && !nodeDB->getMeshNodeByIndex(i)->has_position) {
|
||||
numskipped++;
|
||||
continue;
|
||||
}
|
||||
int xPos = x + (col * columnWidth);
|
||||
int yPos = y + yOffset;
|
||||
renderer(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth);
|
||||
@@ -454,6 +492,9 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
}
|
||||
}
|
||||
|
||||
// This should correct the scrollbar
|
||||
totalEntries -= numskipped;
|
||||
|
||||
#if !defined(M5STACK_UNITC6L)
|
||||
// Draw column separator
|
||||
if (shownCount > 0) {
|
||||
|
||||
@@ -563,6 +563,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 +741,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);
|
||||
}
|
||||
@@ -1000,24 +1000,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 +1091,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 {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "emotes.h"
|
||||
|
||||
namespace graphics
|
||||
@@ -16,6 +18,8 @@ const Emote emotes[] = {
|
||||
{"\U0001F642", Slightly_Smiling, Slightly_Smiling_width, Slightly_Smiling_height}, // 🙂 Slightly Smiling Face
|
||||
{"\U0001F609", Winking_Face, Winking_Face_width, Winking_Face_height}, // 😉 Winking Face
|
||||
{"\U0001F601", Grinning_Smiling_Eyes, Grinning_Smiling_Eyes_width, Grinning_Smiling_Eyes_height}, // 😁 Grinning Smiling Eyes
|
||||
{"\U0001F60D", Heart_eyes, Heart_eyes_width, Heart_eyes_height}, // 😍 Heart Eyes
|
||||
{"\U0001F970", heart_smile, heart_smile_width, heart_smile_height}, // 🥰 Smiling Face with Hearts
|
||||
|
||||
// --- Question/Alert ---
|
||||
{"\u2753", question, question_width, question_height}, // ❓ Question Mark
|
||||
@@ -28,11 +32,15 @@ const Emote emotes[] = {
|
||||
{"\U0001F605", haha, haha_width, haha_height}, // 😅 Smiling with Sweat
|
||||
{"\U0001F604", Grinning_SmilingEyes2, Grinning_SmilingEyes2_width,
|
||||
Grinning_SmilingEyes2_height}, // 😄 Grinning Face with Smiling Eyes
|
||||
{"\U0001F62D", Loudly_Crying_Face, Loudly_Crying_Face_width, Loudly_Crying_Face_height}, // 😭 Loudly Crying Face
|
||||
|
||||
// --- Gestures and People ---
|
||||
{"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
|
||||
{"\U0001F920", cowboy, cowboy_width, cowboy_height}, // 🤠 Cowboy Hat Face
|
||||
{"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // 🎧 Headphones
|
||||
{"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
|
||||
{"\u270C\uFE0F", peace_sign, peace_sign_width, peace_sign_height}, // ✌️ Victory Hand
|
||||
{"\U0001F596", vulcan_salute, vulcan_salute_width, vulcan_salute_height}, // 🖖 Vulcan Salute
|
||||
{"\U0001F64F", Praying, Praying_width, Praying_height}, // 🙏 Praying Hands
|
||||
{"\U0001F920", cowboy, cowboy_width, cowboy_height}, // 🤠 Cowboy Hat Face
|
||||
{"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // 🎧 Headphones
|
||||
|
||||
// --- Weather ---
|
||||
{"\u2600", sun, sun_width, sun_height}, // ☀ Sun (without variation selector)
|
||||
@@ -43,8 +51,12 @@ const Emote emotes[] = {
|
||||
|
||||
// --- Misc Faces ---
|
||||
{"\U0001F608", devil, devil_width, devil_height}, // 😈 Smiling Face with Horns
|
||||
{"\U0001F921", clown, clown_width, clown_height}, // 🤡 Clown Face
|
||||
{"\U0001F916", robo, robo_width, robo_height}, // 🤖 Robot Face
|
||||
|
||||
// --- Hearts (Multiple Unicode Aliases) ---
|
||||
{"\u2665", heart, heart_width, heart_height}, // ♥ Black Heart Suit
|
||||
{"\u2665\uFE0F", heart, heart_width, heart_height}, // ♥️ Black Heart Suit (emoji presentation)
|
||||
{"\u2764\uFE0F", heart, heart_width, heart_height}, // ❤️ Red Heart
|
||||
{"\U0001F9E1", heart, heart_width, heart_height}, // 🧡 Orange Heart
|
||||
{"\U00002763", heart, heart_width, heart_height}, // ❣ Heart Exclamation
|
||||
@@ -55,223 +67,167 @@ const Emote emotes[] = {
|
||||
{"\U0001F498", heart, heart_width, heart_height}, // 💘 Heart with Arrow
|
||||
|
||||
// --- Objects ---
|
||||
{"\U0001F4A9", poo, poo_width, poo_height}, // 💩 Pile of Poo
|
||||
{"\U0001F514", bell_icon, bell_icon_width, bell_icon_height} // 🔔 Bell
|
||||
{"\U0001F4A9", poo, poo_width, poo_height}, // 💩 Pile of Poo
|
||||
{"\U0001F514", bell_icon, bell_icon_width, bell_icon_height}, // 🔔 Bell
|
||||
{"\U0001F36A", cookie, cookie_width, cookie_height}, // 🍪 Cookie
|
||||
{"\U0001F525", Fire, Fire_width, Fire_height}, // 🔥 Fire
|
||||
{"\u2728", Sparkles, Sparkles_width, Sparkles_height}, // ✨ Sparkles
|
||||
{"\U0001F573\uFE0F", hole, hole_width, hole_height}, // 🕳️ Hole
|
||||
{"\U0001F3B3", bowling, bowling_width, bowling_height} // 🎳 Bowling
|
||||
#endif
|
||||
};
|
||||
|
||||
const int numEmotes = sizeof(emotes) / sizeof(emotes[0]);
|
||||
|
||||
#ifndef EXCLUDE_EMOJI
|
||||
const unsigned char thumbup[] PROGMEM = {
|
||||
0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00,
|
||||
0xC0, 0x08, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00,
|
||||
0x0C, 0xCE, 0x7F, 0x00, 0x04, 0x20, 0x80, 0x00, 0x02, 0x20, 0x80, 0x00, 0x02, 0x60, 0xC0, 0x00, 0x01, 0xF8, 0xFF, 0x01,
|
||||
0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0x18, 0x80, 0x00,
|
||||
0x02, 0x30, 0xC0, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x38, 0x20, 0x10, 0x00, 0xE0, 0xCF, 0x1F, 0x00,
|
||||
};
|
||||
const unsigned char thumbup[] PROGMEM = {0x00, 0x03, 0x80, 0x04, 0x80, 0x04, 0x40, 0x04, 0x20, 0x02, 0x18,
|
||||
0x02, 0x06, 0x3F, 0x06, 0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x70,
|
||||
0x06, 0x40, 0x06, 0x30, 0x08, 0x20, 0xF0, 0x1F, 0x00, 0x00};
|
||||
|
||||
const unsigned char thumbdown[] PROGMEM = {
|
||||
0xE0, 0xCF, 0x1F, 0x00, 0x38, 0x20, 0x10, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x02, 0x30, 0xC0, 0x00,
|
||||
0x01, 0x18, 0x80, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01,
|
||||
0x01, 0xF8, 0xFF, 0x01, 0x02, 0x60, 0xC0, 0x00, 0x02, 0x20, 0x80, 0x00, 0x04, 0x20, 0x80, 0x00, 0x0C, 0xCE, 0x7F, 0x00,
|
||||
0x18, 0x02, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0xC0, 0x08, 0x00, 0x00,
|
||||
0x80, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char thumbdown[] PROGMEM = {0xF0, 0x1F, 0x08, 0x20, 0x06, 0x30, 0x06, 0x40, 0x06, 0x70, 0x06,
|
||||
0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x3F, 0x18, 0x02, 0x20, 0x02,
|
||||
0x40, 0x04, 0x80, 0x04, 0x80, 0x04, 0x00, 0x03, 0x00, 0x00};
|
||||
|
||||
const unsigned char Smiling_Eyes[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf,
|
||||
0x7e, 0xf8, 0xc3, 0xdf, 0x3e, 0xf0, 0x81, 0xdf, 0xbf, 0xf7, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0x3f, 0xff,
|
||||
0x6f, 0xff, 0xdf, 0xfe, 0x6f, 0xff, 0xdf, 0xfe, 0x9f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
|
||||
0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0};
|
||||
const unsigned char Smiling_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
||||
0x4A, 0x02, 0x40, 0x02, 0x40, 0x22, 0x44, 0x22, 0x44, 0xC2, 0x43,
|
||||
0x04, 0x20, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Grinning[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf,
|
||||
0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf,
|
||||
0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
const unsigned char Grinning[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
|
||||
0x42, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Slightly_Smiling[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf,
|
||||
0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
|
||||
0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
const unsigned char Slightly_Smiling[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
|
||||
0x42, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Winking_Face[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xf0, 0xff, 0xc3, 0x78, 0xef, 0xc3, 0xc7, 0xb8, 0xdf, 0xbd, 0xcf, 0xfc, 0xf9, 0x7f, 0xcf, 0xfc, 0xf0, 0xff, 0xcf,
|
||||
0xfe, 0xf0, 0xc3, 0xdf, 0xfe, 0xf0, 0x81, 0xdf, 0xff, 0xf0, 0xbf, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
|
||||
0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0};
|
||||
const unsigned char Winking_Face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x20, 0x42,
|
||||
0x46, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Grinning_Smiling_Eyes[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf8, 0xe3, 0xcf, 0x7c, 0xf7, 0xdd, 0xcf,
|
||||
0xbe, 0xef, 0xbe, 0xdf, 0xbe, 0xef, 0xbe, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x5e, 0x55, 0x55, 0xdf, 0x5e, 0x55, 0x55, 0xdf,
|
||||
0x3c, 0x00, 0x80, 0xcf, 0x7c, 0x55, 0xd5, 0xcf, 0xf8, 0x54, 0xe5, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
const unsigned char Grinning_Smiling_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
||||
0x4A, 0x02, 0x40, 0xFA, 0x5F, 0x0A, 0x50, 0x0A, 0x50, 0x12, 0x48,
|
||||
0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char question[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x07, 0x00,
|
||||
0xE0, 0xC3, 0x0F, 0x00, 0xF0, 0x81, 0x0F, 0x00, 0xF0, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x80, 0x0F, 0x00,
|
||||
0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x7C, 0x00, 0x00,
|
||||
0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char heart_smile[] PROGMEM = {0x00, 0x00, 0x6C, 0x07, 0x7C, 0x18, 0x7C, 0x20, 0x38, 0x24, 0x52,
|
||||
0x0A, 0x02, 0xD8, 0x02, 0xF8, 0x22, 0xFC, 0x20, 0x74, 0xDB, 0x23,
|
||||
0x1F, 0x00, 0x1F, 0x20, 0x0E, 0x18, 0xE4, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char bang[] PROGMEM = {
|
||||
0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x07, 0xF8, 0x3F, 0xFF, 0x07, 0xF8, 0x3F,
|
||||
0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F,
|
||||
0xFE, 0x03, 0xF0, 0x1F, 0xFE, 0x03, 0xF0, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F,
|
||||
0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0xFC, 0x03, 0xF0, 0x0F, 0xFE, 0x03, 0xF0, 0x1F,
|
||||
0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xF8, 0x01, 0xE0, 0x07,
|
||||
};
|
||||
const unsigned char Heart_eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x54, 0x2A, 0xFA,
|
||||
0x5F, 0x72, 0x4E, 0x22, 0x44, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48,
|
||||
0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char haha[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xf9, 0xf3, 0xc0,
|
||||
0xf0, 0xfe, 0xef, 0xc1, 0x38, 0xff, 0x9f, 0xc3, 0xd8, 0xff, 0x7f, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xcf,
|
||||
0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xef, 0xff, 0xff, 0xde, 0xe7, 0xff, 0xff, 0xdc, 0xeb, 0xff, 0xff, 0xda,
|
||||
0xed, 0xff, 0xff, 0xd6, 0xee, 0xff, 0xff, 0xce, 0x36, 0x00, 0x80, 0xcd, 0xb8, 0xff, 0xbf, 0xc3, 0x7e, 0x00, 0xc0, 0xdf,
|
||||
0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
const unsigned char question[] PROGMEM = {0xE0, 0x07, 0x10, 0x08, 0x08, 0x10, 0x88, 0x11, 0x48, 0x12, 0x48,
|
||||
0x12, 0x48, 0x12, 0x30, 0x11, 0x80, 0x08, 0x40, 0x04, 0x40, 0x02,
|
||||
0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x40, 0x02, 0x80, 0x01};
|
||||
|
||||
const unsigned char ROFL[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0xc0, 0x00, 0xfc, 0x07, 0xc0, 0x00, 0xff, 0x1f, 0xc0, 0x80, 0xff, 0x7f, 0xc0, 0xc0, 0xff, 0xff, 0xc0,
|
||||
0xe0, 0x9f, 0xff, 0xc1, 0xf0, 0x9f, 0xff, 0xc0, 0xf8, 0x9f, 0x7f, 0xcb, 0xf8, 0x9f, 0xbf, 0xcb, 0xfc, 0x9f, 0xdf, 0xdb,
|
||||
0xfc, 0x1f, 0x08, 0xdc, 0xfe, 0x1f, 0xf8, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0x1e, 0xf0, 0x7f, 0xfe, 0x1e, 0xf0, 0xbf, 0xfe,
|
||||
0xfe, 0xf3, 0xdf, 0xfe, 0xfe, 0xf3, 0x6f, 0xfe, 0xfe, 0xf3, 0x37, 0xfe, 0xfe, 0xeb, 0x1b, 0xfe, 0xfc, 0xef, 0x0d, 0xde,
|
||||
0xfc, 0xe7, 0x06, 0xcf, 0xf8, 0x6b, 0x83, 0xcf, 0xf8, 0x0d, 0xc0, 0xc7, 0xf0, 0xed, 0xff, 0xc7, 0xe0, 0xee, 0xff, 0xc3,
|
||||
0xc0, 0xee, 0xff, 0xc1, 0x80, 0xee, 0xff, 0xc0, 0x00, 0xe6, 0x3f, 0xc0, 0x00, 0xf0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0xc0};
|
||||
const unsigned char bang[] PROGMEM = {0x30, 0x0C, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48,
|
||||
0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x30, 0x0C,
|
||||
0x00, 0x00, 0x30, 0x0C, 0x48, 0x12, 0x30, 0x0C, 0x00, 0x00};
|
||||
|
||||
const unsigned char Smiling_Closed_Eyes[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0x7c, 0xfe, 0xcf, 0xcf, 0xfc, 0xfc, 0xe7, 0xcf,
|
||||
0xfe, 0xf9, 0xf3, 0xdf, 0xfe, 0xf3, 0xf9, 0xdf, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xfc, 0xe7, 0xff, 0x7f, 0xfe, 0xcf, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf,
|
||||
0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
const unsigned char haha[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
||||
0x4A, 0x0A, 0x50, 0x0E, 0x70, 0xF2, 0x4F, 0x12, 0x48, 0x32, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Grinning_SmilingEyes2[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xff, 0xff, 0xc0,
|
||||
0xf0, 0xff, 0xff, 0xc1, 0xf8, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xc7,
|
||||
0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf,
|
||||
0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0x3f, 0x00, 0x80, 0xdf, 0xbe, 0xff, 0xbf, 0xcf, 0x7e, 0x00, 0xc0, 0xcf,
|
||||
0x7c, 0x00, 0xc0, 0xc7, 0xfc, 0x00, 0xe0, 0xc7, 0xf8, 0x01, 0xf0, 0xc3, 0xf8, 0x03, 0xf8, 0xc3, 0xf0, 0xff, 0xff, 0xc1,
|
||||
0xe0, 0xff, 0xff, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
const unsigned char ROFL[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x84, 0x21, 0x84, 0x20, 0x02,
|
||||
0x4C, 0x02, 0x4A, 0x1A, 0x49, 0x8A, 0x48, 0x42, 0x48, 0x22, 0x44,
|
||||
0xE4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char wave_icon[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0xc7,
|
||||
0x00, 0x00, 0x1e, 0xcc, 0x00, 0x00, 0x30, 0xc8, 0x00, 0x00, 0x60, 0xd8, 0x00, 0x08, 0xc0, 0xd0, 0x00, 0x1a, 0x81, 0xd1,
|
||||
0x00, 0x36, 0x03, 0xd3, 0x80, 0x6d, 0x06, 0xd2, 0x00, 0xdb, 0x0c, 0xc2, 0x80, 0xb6, 0x1d, 0xc0, 0x80, 0x6d, 0x1f, 0xc0,
|
||||
0x00, 0xdb, 0x3f, 0xc0, 0x00, 0xf6, 0x7f, 0xc0, 0x00, 0xfc, 0x7f, 0xc0, 0x08, 0xf8, 0x7f, 0xc0, 0x48, 0xf0, 0x7f, 0xc0,
|
||||
0x48, 0xe0, 0x7f, 0xc0, 0xc8, 0xc0, 0x3f, 0xc0, 0x98, 0x81, 0x1f, 0xc0, 0x10, 0x03, 0x00, 0xc0, 0x30, 0x0e, 0x00, 0xc0,
|
||||
0x20, 0x38, 0x00, 0xc0, 0xe0, 0x00, 0x00, 0xc0, 0x80, 0x07, 0x00, 0xc0, 0x00, 0x1e, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0};
|
||||
const unsigned char Smiling_Closed_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x42,
|
||||
0x42, 0x22, 0x44, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char cowboy[] PROGMEM = {
|
||||
0x00, 0x0c, 0x0c, 0xc0, 0x00, 0x02, 0x10, 0xc0, 0x00, 0x01, 0x20, 0xc0, 0xbc, 0x00, 0x40, 0xcf, 0xc2, 0x01, 0xe0, 0xd0,
|
||||
0x01, 0x01, 0x20, 0xe0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0,
|
||||
0xc1, 0x3f, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe1, 0xf2, 0xf3, 0xf3, 0xd3, 0xf4, 0xf1, 0xe3, 0xcb, 0xfc, 0xf1, 0xe3, 0xc7,
|
||||
0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xfb, 0xf7, 0xc7, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xc7,
|
||||
0x70, 0xf8, 0x8f, 0xc3, 0x70, 0x03, 0xb0, 0xc3, 0x70, 0xfe, 0xbf, 0xc3, 0x60, 0x00, 0x80, 0xc1, 0xc0, 0x00, 0xc0, 0xc0,
|
||||
0x80, 0x01, 0x60, 0xc0, 0x00, 0x07, 0x38, 0xc0, 0x00, 0xfe, 0x1f, 0xc0, 0x00, 0xf0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0};
|
||||
const unsigned char Grinning_SmilingEyes2[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
||||
0x4A, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char deadmau5[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00,
|
||||
0x00, 0xFC, 0x03, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0x00,
|
||||
0xE0, 0xFF, 0xFF, 0x01, 0xF0, 0xFF, 0x7F, 0x00, 0xF0, 0xFF, 0xFF, 0x03, 0xF8, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x07,
|
||||
0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFE, 0xFF, 0xFF, 0x00,
|
||||
0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0xFC,
|
||||
0x0F, 0xFF, 0x7F, 0x00, 0xC0, 0xFF, 0x1F, 0xF8, 0x0F, 0xFC, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0xF8, 0x1F, 0xFC, 0x1F, 0x00,
|
||||
0x00, 0xFF, 0x0F, 0xFC, 0x3F, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x1F, 0xFF, 0xFF, 0xFE, 0x01, 0x00, 0x00, 0x00, 0xFC, 0xFF,
|
||||
0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
|
||||
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x80, 0x07, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char Loudly_Crying_Face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x2C, 0x4A,
|
||||
0x52, 0x12, 0x48, 0x12, 0x48, 0x92, 0x49, 0x52, 0x4A, 0x52, 0x4A,
|
||||
0x54, 0x2A, 0x94, 0x29, 0x18, 0x18, 0xF0, 0x0F, 0x00, 0x00};
|
||||
|
||||
const unsigned char sun[] PROGMEM = {
|
||||
0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x30, 0xC0, 0x00, 0x03,
|
||||
0x70, 0x00, 0x80, 0x03, 0xF0, 0x00, 0xC0, 0x03, 0xF0, 0xF8, 0xC7, 0x03, 0xE0, 0xFC, 0xCF, 0x01, 0x00, 0xFE, 0x1F, 0x00,
|
||||
0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x8E, 0xFF, 0x7F, 0x1C, 0x9F, 0xFF, 0x7F, 0x3E,
|
||||
0x9F, 0xFF, 0x7F, 0x3E, 0x8E, 0xFF, 0x7F, 0x1C, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00,
|
||||
0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0xC0, 0xF9, 0xE7, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0xF0, 0x01, 0xE0, 0x03,
|
||||
0xF0, 0xC0, 0xC0, 0x03, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char wave_icon[] PROGMEM = {0x00, 0x00, 0xC0, 0x18, 0x30, 0x21, 0x48, 0x5A, 0x94, 0x64, 0x24,
|
||||
0x25, 0x4A, 0x24, 0x12, 0x44, 0x22, 0x44, 0x04, 0x40, 0x08, 0x40,
|
||||
0x12, 0x40, 0x22, 0x20, 0xC4, 0x10, 0x18, 0x0F, 0x00, 0x00};
|
||||
|
||||
const unsigned char rain[] PROGMEM = {
|
||||
0xC0, 0x0F, 0xC0, 0x00, 0x40, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x03, 0x38, 0x00,
|
||||
0x00, 0x0E, 0x0C, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x20,
|
||||
0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00,
|
||||
0x00, 0x10, 0x06, 0x00, 0x00, 0x08, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x01, 0x80, 0x00, 0x01, 0x00,
|
||||
0xC0, 0xC0, 0x81, 0x03, 0xA0, 0x60, 0xC1, 0x03, 0x90, 0x20, 0x41, 0x01, 0xF0, 0xE0, 0xC0, 0x01, 0x60, 0x4C,
|
||||
0x98, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x0B, 0x12, 0x00, 0x00, 0x09, 0x1A, 0x00, 0x00, 0x06, 0x0E, 0x00,
|
||||
};
|
||||
const unsigned char cowboy[] PROGMEM = {0x70, 0x0E, 0x8F, 0xF1, 0x11, 0x88, 0x21, 0x84, 0xC2, 0x43, 0x1E,
|
||||
0x78, 0xE2, 0x47, 0x42, 0x42, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char cloud[] PROGMEM = {
|
||||
0x00, 0x80, 0x07, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x70, 0x30, 0x00, 0x00, 0x10, 0x60, 0x00, 0x80, 0x1F, 0x40, 0x00,
|
||||
0xC0, 0x0F, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x00, 0x60, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x01,
|
||||
0x20, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10,
|
||||
0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20,
|
||||
0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x10,
|
||||
0x02, 0x00, 0x00, 0x10, 0x06, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03,
|
||||
};
|
||||
const unsigned char deadmau5[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0xE4, 0x27, 0x12, 0x48, 0x0A,
|
||||
0x50, 0x0E, 0x70, 0x11, 0x88, 0x19, 0x98, 0x19, 0x98, 0x19, 0x98,
|
||||
0x19, 0x98, 0x19, 0x98, 0x11, 0x88, 0x0E, 0x70, 0x00, 0x00};
|
||||
|
||||
const unsigned char fog[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x3C, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01,
|
||||
0x00, 0x38, 0x00, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0xFF, 0x83, 0xFF, 0x01, 0x03, 0xFF, 0x81, 0x01, 0x00, 0x7C, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x3E, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01, 0x00, 0x38, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char sun[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0xEC, 0x37, 0xFC, 0x3F, 0xF8, 0x1F, 0xFC,
|
||||
0x3F, 0xFE, 0x7F, 0xFC, 0x3F, 0xFC, 0x3F, 0xFE, 0x7F, 0xFC, 0x3F,
|
||||
0xF8, 0x1F, 0xFC, 0x3F, 0xEC, 0x37, 0x80, 0x01, 0x00, 0x00};
|
||||
|
||||
const unsigned char devil[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x0f, 0xfc, 0x0f, 0xfc,
|
||||
0x3f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfc, 0xff, 0xff, 0xcf,
|
||||
0xfc, 0xff, 0xff, 0xcf, 0xf8, 0xff, 0xff, 0xc7, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xf1, 0xe3, 0xc3,
|
||||
0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xe0, 0xfd, 0xef, 0xc1, 0xe0, 0xf3, 0xf3, 0xc1, 0xc0, 0x07, 0xf8, 0xc0, 0x80, 0x1f, 0x7e, 0xc0,
|
||||
0x00, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0};
|
||||
const unsigned char rain[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC, 0x3F, 0xFE,
|
||||
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0x00, 0x00, 0x48, 0x12,
|
||||
0x48, 0x12, 0x24, 0x09, 0x24, 0x09, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
const unsigned char heart[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xF0, 0x00, 0xF8, 0x0F, 0xFC, 0x07, 0xFC, 0x1F, 0x06, 0x0E, 0xFE, 0x3F, 0x03, 0x18,
|
||||
0xFE, 0xFF, 0x7F, 0x10, 0xFF, 0xFF, 0xFF, 0x31, 0xFF, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0xFF, 0x37, 0xFF, 0xFF, 0xFF, 0x37,
|
||||
0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F,
|
||||
0xFC, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03, 0xF0, 0xFF, 0xFF, 0x03,
|
||||
0xE0, 0xFF, 0xFF, 0x01, 0xC0, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0xFE, 0x1F, 0x00,
|
||||
0x00, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char cloud[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC,
|
||||
0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0xF8, 0x1F,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
const unsigned char poo[] PROGMEM = {
|
||||
0x00, 0x1c, 0x00, 0xc0, 0x00, 0x7c, 0x00, 0xc0, 0x00, 0xfc, 0x00, 0xc0, 0x00, 0x7c, 0x03, 0xc0, 0x00, 0xbe, 0x03, 0xc0,
|
||||
0x00, 0xdf, 0x0f, 0xc0, 0x80, 0xcf, 0x0f, 0xc0, 0xc0, 0xf1, 0x0f, 0xc0, 0x60, 0xfc, 0x0f, 0xc0, 0x30, 0xff, 0x07, 0xc0,
|
||||
0x90, 0xff, 0x3b, 0xc0, 0xc0, 0xff, 0x7d, 0xc0, 0xf8, 0xff, 0xfc, 0xc0, 0xf8, 0x3f, 0xf0, 0xc0, 0x78, 0x88, 0xc0, 0xc0,
|
||||
0x20, 0xe3, 0x18, 0xc0, 0x98, 0xe7, 0xbc, 0xc1, 0x9c, 0x64, 0xa4, 0xc3, 0x9e, 0x64, 0xa4, 0xc7, 0xbe, 0xe4, 0xa4, 0xc7,
|
||||
0xbc, 0x27, 0xbc, 0xc7, 0x38, 0x03, 0xd9, 0xc3, 0x00, 0xf0, 0x63, 0xc0, 0xf8, 0xfc, 0x3f, 0xcf, 0xfc, 0xff, 0x87, 0xdf,
|
||||
0xfe, 0xff, 0xe0, 0xdf, 0xfc, 0x1f, 0xfe, 0xdf, 0xf8, 0x07, 0xf8, 0xcf, 0xf0, 0x03, 0xe0, 0xc7, 0x00, 0x00, 0x00, 0xc0};
|
||||
const unsigned char fog[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x54, 0x55, 0x22, 0x22, 0x00,
|
||||
0x00, 0x44, 0x44, 0xAA, 0x2A, 0x11, 0x11, 0x00, 0x00, 0x88, 0x88,
|
||||
0x54, 0x55, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
const unsigned char bell_icon[] PROGMEM = {
|
||||
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b11110000,
|
||||
0b00000011, 0b00000000, 0b00000000, 0b11111100, 0b00001111, 0b00000000, 0b00000000, 0b00001111, 0b00111100, 0b00000000,
|
||||
0b00000000, 0b00000011, 0b00110000, 0b00000000, 0b10000000, 0b00000001, 0b01100000, 0b00000000, 0b11000000, 0b00000000,
|
||||
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
|
||||
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
|
||||
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
|
||||
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b01000000, 0b00000000, 0b10000000, 0b00000000, 0b01100000, 0b00000000,
|
||||
0b10000000, 0b00000001, 0b01110000, 0b00000000, 0b10000000, 0b00000011, 0b00110000, 0b00000000, 0b00000000, 0b00000011,
|
||||
0b00011000, 0b00000000, 0b00000000, 0b00000110, 0b11110000, 0b11111111, 0b11111111, 0b00000011, 0b00000000, 0b00001100,
|
||||
0b00001100, 0b00000000, 0b00000000, 0b00011000, 0b00000110, 0b00000000, 0b00000000, 0b11111000, 0b00000111, 0b00000000,
|
||||
0b00000000, 0b11100000, 0b00000001, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
|
||||
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000};
|
||||
const unsigned char devil[] PROGMEM = {0x06, 0x60, 0xCA, 0x53, 0x32, 0x4C, 0x22, 0x44, 0x44, 0x22, 0x3A,
|
||||
0x5C, 0x32, 0x4C, 0x52, 0x4A, 0x72, 0x4E, 0x02, 0x40, 0x22, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char heart[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x3C, 0x3C, 0x7E, 0x7E, 0xFE, 0x7F, 0xFE,
|
||||
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0xF8, 0x1F, 0xF8, 0x1F,
|
||||
0xF0, 0x0F, 0xE0, 0x07, 0xC0, 0x03, 0x80, 0x01, 0x00, 0x00};
|
||||
|
||||
const unsigned char poo[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x20, 0x04, 0x10, 0x04, 0xF0,
|
||||
0x08, 0x10, 0x10, 0x48, 0x12, 0x08, 0x18, 0xE8, 0x21, 0x1C, 0x40,
|
||||
0x42, 0x42, 0x82, 0x41, 0x02, 0x30, 0xFC, 0x0F, 0x00, 0x00};
|
||||
|
||||
const unsigned char bell_icon[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0xE0, 0x07, 0xF0, 0x0F, 0xF0,
|
||||
0x0F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFC, 0x3F,
|
||||
0xFC, 0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0x80, 0x01, 0x00, 0x00};
|
||||
|
||||
const unsigned char cookie[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x22, 0x32,
|
||||
0x40, 0x02, 0x58, 0x82, 0x5B, 0x92, 0x43, 0x82, 0x43, 0x02, 0x40,
|
||||
0x64, 0x28, 0x64, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Fire[] PROGMEM = {0x30, 0x00, 0xF0, 0x00, 0xF8, 0x03, 0xF8, 0x07, 0xFC, 0x1F, 0xFC,
|
||||
0x1F, 0xFE, 0x3E, 0x7E, 0x3E, 0x3E, 0x7C, 0x1E, 0x78, 0x1E, 0x70,
|
||||
0x1C, 0x70, 0x1C, 0x70, 0x38, 0x38, 0x30, 0x38, 0x60, 0x0C};
|
||||
|
||||
const unsigned char peace_sign[] PROGMEM = {0xC0, 0x30, 0x40, 0x29, 0x40, 0x25, 0x40, 0x15, 0x40, 0x12, 0x38,
|
||||
0x0A, 0x54, 0x68, 0x54, 0x58, 0x54, 0x44, 0x3C, 0x22, 0x04, 0x22,
|
||||
0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Praying[] PROGMEM = {0x00, 0x00, 0x40, 0x02, 0xA0, 0x05, 0x90, 0x09, 0x90, 0x09, 0x90,
|
||||
0x09, 0x98, 0x19, 0x94, 0x29, 0xA4, 0x25, 0xA4, 0x25, 0x84, 0x21,
|
||||
0x84, 0x21, 0x86, 0x61, 0x4E, 0x72, 0x7F, 0x7E, 0x3F, 0xFC};
|
||||
|
||||
const unsigned char Sparkles[] PROGMEM = {0x00, 0x00, 0x10, 0x00, 0x38, 0x04, 0x10, 0x04, 0x00, 0x0E, 0x00,
|
||||
0x1F, 0x80, 0x3F, 0xE0, 0xFF, 0x80, 0x3F, 0x10, 0x1F, 0x10, 0x0E,
|
||||
0x38, 0x04, 0xFE, 0x04, 0x38, 0x00, 0x10, 0x00, 0x10, 0x00};
|
||||
|
||||
const unsigned char clown[] PROGMEM = {0x00, 0x00, 0xEE, 0x77, 0x1A, 0x58, 0x06, 0x60, 0x24, 0x24, 0x72,
|
||||
0x4E, 0x22, 0x44, 0x82, 0x41, 0x82, 0x41, 0x1A, 0x58, 0xF2, 0x4F,
|
||||
0x14, 0x28, 0xE4, 0x27, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char robo[] PROGMEM = {0x80, 0x01, 0xC0, 0x03, 0x80, 0x01, 0xFC, 0x3F, 0x04, 0x20, 0x74,
|
||||
0x2E, 0x52, 0x4A, 0x72, 0x4E, 0x02, 0x40, 0x02, 0x40, 0xA2, 0x4A,
|
||||
0x52, 0x45, 0x04, 0x20, 0x04, 0x20, 0xFC, 0x3F, 0x00, 0x00};
|
||||
|
||||
const unsigned char hole[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x3C, 0x3C,
|
||||
0x06, 0x60, 0x0C, 0x30, 0xF0, 0x0F, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
const unsigned char bowling[] PROGMEM = {0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00,
|
||||
0x38, 0x00, 0x28, 0x78, 0x44, 0x84, 0x82, 0x22, 0x83, 0x52, 0x83,
|
||||
0x02, 0x83, 0x02, 0x45, 0x84, 0x44, 0x78, 0x38, 0x00, 0x00};
|
||||
|
||||
const unsigned char vulcan_salute[] PROGMEM = {0x08, 0x02, 0x16, 0x0D, 0x15, 0x15, 0x15, 0x15, 0xA9, 0x12, 0x4A,
|
||||
0x0A, 0x02, 0x38, 0x04, 0x48, 0x04, 0x44, 0x04, 0x22, 0x04, 0x22,
|
||||
0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
|
||||
#endif
|
||||
|
||||
} // namespace graphics
|
||||
#endif
|
||||
@@ -17,98 +17,150 @@ extern const int numEmotes;
|
||||
|
||||
#ifndef EXCLUDE_EMOJI
|
||||
// === Emote Bitmaps ===
|
||||
#define thumbs_height 25
|
||||
#define thumbs_width 25
|
||||
#define thumbs_height 16
|
||||
#define thumbs_width 16
|
||||
extern const unsigned char thumbup[] PROGMEM;
|
||||
extern const unsigned char thumbdown[] PROGMEM;
|
||||
|
||||
#define Smiling_Eyes_height 30
|
||||
#define Smiling_Eyes_width 30
|
||||
#define Smiling_Eyes_height 16
|
||||
#define Smiling_Eyes_width 16
|
||||
extern const unsigned char Smiling_Eyes[] PROGMEM;
|
||||
|
||||
#define Grinning_height 30
|
||||
#define Grinning_width 30
|
||||
#define Grinning_height 16
|
||||
#define Grinning_width 16
|
||||
extern const unsigned char Grinning[] PROGMEM;
|
||||
|
||||
#define Slightly_Smiling_height 30
|
||||
#define Slightly_Smiling_width 30
|
||||
#define Slightly_Smiling_height 16
|
||||
#define Slightly_Smiling_width 16
|
||||
extern const unsigned char Slightly_Smiling[] PROGMEM;
|
||||
|
||||
#define Winking_Face_height 30
|
||||
#define Winking_Face_width 30
|
||||
#define Winking_Face_height 16
|
||||
#define Winking_Face_width 16
|
||||
extern const unsigned char Winking_Face[] PROGMEM;
|
||||
|
||||
#define Grinning_Smiling_Eyes_height 30
|
||||
#define Grinning_Smiling_Eyes_width 30
|
||||
#define Grinning_Smiling_Eyes_height 16
|
||||
#define Grinning_Smiling_Eyes_width 16
|
||||
extern const unsigned char Grinning_Smiling_Eyes[] PROGMEM;
|
||||
|
||||
#define question_height 25
|
||||
#define question_width 25
|
||||
#define heart_smile_height 16
|
||||
#define heart_smile_width 16
|
||||
extern const unsigned char heart_smile[] PROGMEM;
|
||||
|
||||
#define Heart_eyes_height 16
|
||||
#define Heart_eyes_width 16
|
||||
extern const unsigned char Heart_eyes[] PROGMEM;
|
||||
|
||||
#define question_height 16
|
||||
#define question_width 16
|
||||
extern const unsigned char question[] PROGMEM;
|
||||
|
||||
#define bang_height 30
|
||||
#define bang_width 30
|
||||
#define bang_height 16
|
||||
#define bang_width 16
|
||||
extern const unsigned char bang[] PROGMEM;
|
||||
|
||||
#define haha_height 30
|
||||
#define haha_width 30
|
||||
#define haha_height 16
|
||||
#define haha_width 16
|
||||
extern const unsigned char haha[] PROGMEM;
|
||||
|
||||
#define ROFL_height 30
|
||||
#define ROFL_width 30
|
||||
#define ROFL_height 16
|
||||
#define ROFL_width 16
|
||||
extern const unsigned char ROFL[] PROGMEM;
|
||||
|
||||
#define Smiling_Closed_Eyes_height 30
|
||||
#define Smiling_Closed_Eyes_width 30
|
||||
#define Smiling_Closed_Eyes_height 16
|
||||
#define Smiling_Closed_Eyes_width 16
|
||||
extern const unsigned char Smiling_Closed_Eyes[] PROGMEM;
|
||||
|
||||
#define Grinning_SmilingEyes2_height 30
|
||||
#define Grinning_SmilingEyes2_width 30
|
||||
#define Grinning_SmilingEyes2_height 16
|
||||
#define Grinning_SmilingEyes2_width 16
|
||||
extern const unsigned char Grinning_SmilingEyes2[] PROGMEM;
|
||||
|
||||
#define wave_icon_height 30
|
||||
#define wave_icon_width 30
|
||||
#define Loudly_Crying_Face_height 16
|
||||
#define Loudly_Crying_Face_width 16
|
||||
extern const unsigned char Loudly_Crying_Face[] PROGMEM;
|
||||
|
||||
#define wave_icon_height 16
|
||||
#define wave_icon_width 16
|
||||
extern const unsigned char wave_icon[] PROGMEM;
|
||||
|
||||
#define cowboy_height 30
|
||||
#define cowboy_width 30
|
||||
#define cowboy_height 16
|
||||
#define cowboy_width 16
|
||||
extern const unsigned char cowboy[] PROGMEM;
|
||||
|
||||
#define deadmau5_height 30
|
||||
#define deadmau5_width 60
|
||||
#define deadmau5_height 16
|
||||
#define deadmau5_width 16
|
||||
extern const unsigned char deadmau5[] PROGMEM;
|
||||
|
||||
#define sun_height 30
|
||||
#define sun_width 30
|
||||
#define sun_height 16
|
||||
#define sun_width 16
|
||||
extern const unsigned char sun[] PROGMEM;
|
||||
|
||||
#define rain_height 30
|
||||
#define rain_width 30
|
||||
#define rain_height 16
|
||||
#define rain_width 16
|
||||
extern const unsigned char rain[] PROGMEM;
|
||||
|
||||
#define cloud_height 30
|
||||
#define cloud_width 30
|
||||
#define cloud_height 16
|
||||
#define cloud_width 16
|
||||
extern const unsigned char cloud[] PROGMEM;
|
||||
|
||||
#define fog_height 25
|
||||
#define fog_width 25
|
||||
#define fog_height 16
|
||||
#define fog_width 16
|
||||
extern const unsigned char fog[] PROGMEM;
|
||||
|
||||
#define devil_height 30
|
||||
#define devil_width 30
|
||||
#define devil_height 16
|
||||
#define devil_width 16
|
||||
extern const unsigned char devil[] PROGMEM;
|
||||
|
||||
#define heart_height 30
|
||||
#define heart_width 30
|
||||
#define heart_height 16
|
||||
#define heart_width 16
|
||||
extern const unsigned char heart[] PROGMEM;
|
||||
|
||||
#define poo_height 30
|
||||
#define poo_width 30
|
||||
#define poo_height 16
|
||||
#define poo_width 16
|
||||
extern const unsigned char poo[] PROGMEM;
|
||||
|
||||
#define bell_icon_width 30
|
||||
#define bell_icon_height 30
|
||||
#define bell_icon_width 16
|
||||
#define bell_icon_height 16
|
||||
extern const unsigned char bell_icon[] PROGMEM;
|
||||
|
||||
#define cookie_width 16
|
||||
#define cookie_height 16
|
||||
extern const unsigned char cookie[] PROGMEM;
|
||||
|
||||
#define Fire_width 16
|
||||
#define Fire_height 16
|
||||
extern const unsigned char Fire[] PROGMEM;
|
||||
|
||||
#define peace_sign_width 16
|
||||
#define peace_sign_height 16
|
||||
extern const unsigned char peace_sign[] PROGMEM;
|
||||
|
||||
#define Praying_width 16
|
||||
#define Praying_height 16
|
||||
extern const unsigned char Praying[] PROGMEM;
|
||||
|
||||
#define Sparkles_width 16
|
||||
#define Sparkles_height 16
|
||||
extern const unsigned char Sparkles[] PROGMEM;
|
||||
|
||||
#define clown_width 16
|
||||
#define clown_height 16
|
||||
extern const unsigned char clown[] PROGMEM;
|
||||
|
||||
#define robo_width 16
|
||||
#define robo_height 16
|
||||
extern const unsigned char robo[] PROGMEM;
|
||||
|
||||
#define hole_width 16
|
||||
#define hole_height 16
|
||||
extern const unsigned char hole[] PROGMEM;
|
||||
|
||||
#define bowling_width 16
|
||||
#define bowling_height 16
|
||||
extern const unsigned char bowling[] PROGMEM;
|
||||
|
||||
#define vulcan_salute_width 16
|
||||
#define vulcan_salute_height 16
|
||||
extern const unsigned char vulcan_salute[] PROGMEM;
|
||||
#endif // EXCLUDE_EMOJI
|
||||
|
||||
} // namespace graphics
|
||||
} // namespace graphics
|
||||
@@ -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,116 +165,129 @@ 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;
|
||||
float westernmost = lngCenter;
|
||||
|
||||
for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Skip if no position
|
||||
@@ -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);
|
||||
@@ -348,8 +474,8 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||
// Need at least two, to draw a sensible map
|
||||
bool InkHUD::MapApplet::enoughMarkers()
|
||||
{
|
||||
uint8_t count = 0;
|
||||
for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Count nodes
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ int32_t ButtonThread::runOnce()
|
||||
if (!userButton.isIdle() || waitingForLongPress) {
|
||||
return 50;
|
||||
}
|
||||
return INT32_MAX;
|
||||
return 100; // FIXME: Why can't we rely on interrupts and use INT32_MAX here?
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -3,16 +3,66 @@
|
||||
|
||||
InputBroker *inputBroker = nullptr;
|
||||
|
||||
InputBroker::InputBroker(){};
|
||||
InputBroker::InputBroker()
|
||||
{
|
||||
#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);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InputBroker::registerSource(Observable<const InputEvent *> *source)
|
||||
{
|
||||
this->inputEventObserver.observe(source);
|
||||
}
|
||||
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
void InputBroker::requestPollSoon(InputPollable *pollable)
|
||||
{
|
||||
if (xPortInIsrContext() == pdTRUE) {
|
||||
xQueueSendFromISR(pollSoonQueue, &pollable, NULL);
|
||||
} else {
|
||||
xQueueSend(pollSoonQueue, &pollable, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void InputBroker::queueInputEvent(const InputEvent *event)
|
||||
{
|
||||
if (xPortInIsrContext() == pdTRUE) {
|
||||
xQueueSendFromISR(inputEventQueue, event, NULL);
|
||||
} else {
|
||||
xQueueSend(inputEventQueue, event, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void InputBroker::processInputEventQueue()
|
||||
{
|
||||
InputEvent event;
|
||||
while (xQueueReceive(inputEventQueue, &event, 0)) {
|
||||
handleInputEvent(&event);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int InputBroker::handleInputEvent(const InputEvent *event)
|
||||
{
|
||||
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
|
||||
this->notifyObservers(event);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
void InputBroker::pollSoonWorker(void *p)
|
||||
{
|
||||
InputBroker *instance = (InputBroker *)p;
|
||||
while (true) {
|
||||
InputPollable *pollable = NULL;
|
||||
xQueueReceive(instance->pollSoonQueue, &pollable, portMAX_DELAY);
|
||||
if (pollable) {
|
||||
pollable->pollOnce();
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Observer.h"
|
||||
#include "freertosinc.h"
|
||||
|
||||
enum input_broker_event {
|
||||
INPUT_BROKER_NONE = 0,
|
||||
@@ -41,6 +43,13 @@ typedef struct _InputEvent {
|
||||
uint16_t touchX;
|
||||
uint16_t touchY;
|
||||
} InputEvent;
|
||||
|
||||
class InputPollable
|
||||
{
|
||||
public:
|
||||
virtual void pollOnce() = 0;
|
||||
};
|
||||
|
||||
class InputBroker : public Observable<const InputEvent *>
|
||||
{
|
||||
CallbackObserver<InputBroker, const InputEvent *> inputEventObserver =
|
||||
@@ -50,9 +59,22 @@ class InputBroker : public Observable<const InputEvent *>
|
||||
InputBroker();
|
||||
void registerSource(Observable<const InputEvent *> *source);
|
||||
void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
void requestPollSoon(InputPollable *pollable);
|
||||
void queueInputEvent(const InputEvent *event);
|
||||
void processInputEventQueue();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
int handleInputEvent(const InputEvent *event);
|
||||
|
||||
private:
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
QueueHandle_t inputEventQueue;
|
||||
QueueHandle_t pollSoonQueue;
|
||||
TaskHandle_t pollSoonTask;
|
||||
static void pollSoonWorker(void *p);
|
||||
#endif
|
||||
};
|
||||
|
||||
extern InputBroker *inputBroker;
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
RotaryEncoderImpl *rotaryEncoderImpl;
|
||||
|
||||
RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME)
|
||||
RotaryEncoderImpl::RotaryEncoderImpl()
|
||||
{
|
||||
rotary = nullptr;
|
||||
}
|
||||
@@ -18,7 +18,6 @@ bool RotaryEncoderImpl::init()
|
||||
if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
|
||||
moduleConfig.canned_message.inputbroker_pin_b == 0) {
|
||||
// Input device is disabled.
|
||||
disable();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -30,7 +29,11 @@ bool RotaryEncoderImpl::init()
|
||||
moduleConfig.canned_message.inputbroker_pin_press);
|
||||
rotary->resetButton();
|
||||
|
||||
inputBroker->registerSource(this);
|
||||
interruptInstance = this;
|
||||
auto interruptHandler = []() { inputBroker->requestPollSoon(interruptInstance); };
|
||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
|
||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
|
||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
|
||||
|
||||
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
|
||||
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
|
||||
@@ -38,36 +41,36 @@ bool RotaryEncoderImpl::init()
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t RotaryEncoderImpl::runOnce()
|
||||
void RotaryEncoderImpl::pollOnce()
|
||||
{
|
||||
InputEvent e{originName, INPUT_BROKER_NONE, 0, 0, 0};
|
||||
InputEvent e{ORIGIN_NAME, INPUT_BROKER_NONE, 0, 0, 0};
|
||||
|
||||
static uint32_t lastPressed = millis();
|
||||
if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
|
||||
if (lastPressed + 200 < millis()) {
|
||||
LOG_DEBUG("Rotary event Press");
|
||||
lastPressed = millis();
|
||||
e.inputEvent = this->eventPressed;
|
||||
}
|
||||
} else {
|
||||
switch (rotary->process()) {
|
||||
case RotaryEncoder::DIRECTION_CW:
|
||||
LOG_DEBUG("Rotary event CW");
|
||||
e.inputEvent = this->eventCw;
|
||||
break;
|
||||
case RotaryEncoder::DIRECTION_CCW:
|
||||
LOG_DEBUG("Rotary event CCW");
|
||||
e.inputEvent = this->eventCcw;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
inputBroker->queueInputEvent(&e);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
switch (rotary->process()) {
|
||||
case RotaryEncoder::DIRECTION_CW:
|
||||
LOG_DEBUG("Rotary event CW");
|
||||
e.inputEvent = this->eventCw;
|
||||
inputBroker->queueInputEvent(&e);
|
||||
break;
|
||||
case RotaryEncoder::DIRECTION_CCW:
|
||||
LOG_DEBUG("Rotary event CCW");
|
||||
e.inputEvent = this->eventCcw;
|
||||
inputBroker->queueInputEvent(&e);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 10;
|
||||
}
|
||||
|
||||
RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
|
||||
// This is a version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
|
||||
|
||||
#include "InputBroker.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
@@ -8,21 +8,21 @@
|
||||
|
||||
class RotaryEncoder;
|
||||
|
||||
class RotaryEncoderImpl : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
class RotaryEncoderImpl : public InputPollable
|
||||
{
|
||||
public:
|
||||
RotaryEncoderImpl();
|
||||
bool init(void);
|
||||
virtual void pollOnce() override;
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
static RotaryEncoderImpl *interruptInstance;
|
||||
|
||||
input_broker_event eventCw = INPUT_BROKER_NONE;
|
||||
input_broker_event eventCcw = INPUT_BROKER_NONE;
|
||||
input_broker_event eventPressed = INPUT_BROKER_NONE;
|
||||
|
||||
RotaryEncoder *rotary;
|
||||
const char *originName;
|
||||
};
|
||||
|
||||
extern RotaryEncoderImpl *rotaryEncoderImpl;
|
||||
|
||||
62
src/main.cpp
62
src/main.cpp
@@ -23,6 +23,7 @@
|
||||
#include "power.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
#include "detect/ScanI2CConsumer.h"
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#include <Wire.h>
|
||||
#endif
|
||||
@@ -297,6 +298,12 @@ void printInfo()
|
||||
#ifndef PIO_UNIT_TESTING
|
||||
void setup()
|
||||
{
|
||||
#if defined(R1_NEO)
|
||||
pinMode(DCDC_EN_HOLD, OUTPUT);
|
||||
digitalWrite(DCDC_EN_HOLD, HIGH);
|
||||
pinMode(NRF_ON, OUTPUT);
|
||||
digitalWrite(NRF_ON, HIGH);
|
||||
#endif
|
||||
|
||||
#if defined(PIN_POWER_EN)
|
||||
pinMode(PIN_POWER_EN, OUTPUT);
|
||||
@@ -375,7 +382,7 @@ void setup()
|
||||
io.pinMode(EXPANDS_DRV_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
|
||||
io.pinMode(EXPANDS_AMP_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
|
||||
io.digitalWrite(EXPANDS_AMP_EN, LOW);
|
||||
io.pinMode(EXPANDS_LORA_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_LORA_EN, HIGH);
|
||||
io.pinMode(EXPANDS_GPS_EN, OUTPUT);
|
||||
@@ -429,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)
|
||||
@@ -712,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
|
||||
@@ -859,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)
|
||||
@@ -958,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);
|
||||
@@ -1594,8 +1595,13 @@ void loop()
|
||||
#endif
|
||||
|
||||
service->loop();
|
||||
#if defined(LGFX_SDL)
|
||||
if (screen) {
|
||||
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
if (inputBroker)
|
||||
inputBroker->processInputEventQueue();
|
||||
#endif
|
||||
#if ARCH_PORTDUINO && HAS_TFT
|
||||
if (screen && portduino_config.displayPanel == x11 &&
|
||||
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
auto dispdev = screen->getDisplayDevice();
|
||||
if (dispdev)
|
||||
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
#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_network_ipv6_enabled false
|
||||
|
||||
#define default_mqtt_address "mqtt.meshtastic.org"
|
||||
#define default_mqtt_username "meshdev"
|
||||
@@ -46,12 +47,15 @@ class Default
|
||||
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval);
|
||||
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
|
||||
static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
|
||||
// Note: numOnlineNodes uses uint32_t to match the public API and allow flexibility,
|
||||
// even though internal node counts use uint16_t (max 65535 nodes)
|
||||
static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes);
|
||||
static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured);
|
||||
static uint32_t getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue);
|
||||
|
||||
private:
|
||||
static float congestionScalingCoefficient(int numOnlineNodes)
|
||||
// Note: Kept as uint32_t to match the public API parameter type
|
||||
static float congestionScalingCoefficient(uint32_t numOnlineNodes)
|
||||
{
|
||||
// Increase frequency of broadcasts for small networks regardless of preset
|
||||
if (numOnlineNodes <= 10) {
|
||||
@@ -84,4 +88,4 @@ class Default
|
||||
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default)
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -31,33 +31,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected
|
||||
|
||||
// Handle hop_limit upgrade scenario for rebroadcasters
|
||||
// isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
|
||||
if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
|
||||
// wasSeenRecently() reports false in upgrade cases so we handle replacement before the duplicate short-circuit
|
||||
// If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
|
||||
// rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
|
||||
uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
|
||||
if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
|
||||
LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
|
||||
p->hop_limit, dropThreshold);
|
||||
|
||||
if (nodeDB)
|
||||
nodeDB->updateFrom(*p);
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
|
||||
traceRouteModule->processUpgradedPacket(*p);
|
||||
#endif
|
||||
|
||||
perhapsRebroadcast(p);
|
||||
|
||||
// We already enqueued the improved copy, so make sure the incoming packet stops here.
|
||||
return true;
|
||||
}
|
||||
|
||||
// No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
|
||||
// delivering the same packet to applications/phone twice with different hop limits.
|
||||
seenRecently = true;
|
||||
if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
|
||||
return true; // we handled it, so stop processing
|
||||
}
|
||||
|
||||
if (seenRecently) {
|
||||
@@ -70,8 +45,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
if (isRepeated) {
|
||||
LOG_DEBUG("Repeated reliable tx");
|
||||
// Check if it's still in the Tx queue, if not, we have to relay it again
|
||||
if (!findInTxQueue(p->from, p->id))
|
||||
if (!findInTxQueue(p->from, p->id)) {
|
||||
reprocessPacket(p);
|
||||
perhapsRebroadcast(p);
|
||||
}
|
||||
} else {
|
||||
perhapsCancelDupe(p);
|
||||
}
|
||||
@@ -82,6 +59,40 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
return Router::shouldFilterReceived(p);
|
||||
}
|
||||
|
||||
bool FloodingRouter::perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
// isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
|
||||
if (isRebroadcaster() && iface && p->hop_limit > 0) {
|
||||
// If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
|
||||
// rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
|
||||
uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
|
||||
if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
|
||||
LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
|
||||
p->hop_limit, dropThreshold);
|
||||
|
||||
reprocessPacket(p);
|
||||
perhapsRebroadcast(p);
|
||||
|
||||
rxDupe++;
|
||||
// We already enqueued the improved copy, so make sure the incoming packet stops here.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (nodeDB)
|
||||
nodeDB->updateFrom(*p);
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
|
||||
traceRouteModule->processUpgradedPacket(*p);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
|
||||
@@ -121,41 +132,6 @@ bool FloodingRouter::isRebroadcaster()
|
||||
config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
|
||||
}
|
||||
|
||||
void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
|
||||
if (p->id != 0) {
|
||||
if (isRebroadcaster()) {
|
||||
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
|
||||
|
||||
// Use shared logic to determine if hop_limit should be decremented
|
||||
if (shouldDecrementHopLimit(p)) {
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
} else {
|
||||
LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit");
|
||||
}
|
||||
#if USERPREFS_EVENT_MODE
|
||||
if (tosend->hop_limit > 2) {
|
||||
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
|
||||
tosend->hop_start -= (tosend->hop_limit - 2);
|
||||
tosend->hop_limit = 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
|
||||
|
||||
LOG_INFO("Rebroadcast received floodmsg");
|
||||
// Note: we are careful to resend using the original senders node id
|
||||
send(tosend);
|
||||
} else {
|
||||
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG("Ignore 0 id broadcast");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
||||
{
|
||||
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
|
||||
|
||||
@@ -27,10 +27,6 @@
|
||||
*/
|
||||
class FloodingRouter : public Router
|
||||
{
|
||||
private:
|
||||
/* Check if we should rebroadcast this packet, and do so if needed */
|
||||
void perhapsRebroadcast(const meshtastic_MeshPacket *p);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
@@ -59,6 +55,17 @@ class FloodingRouter : public Router
|
||||
*/
|
||||
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
|
||||
|
||||
/* Check if we should rebroadcast this packet, and do so if needed */
|
||||
virtual bool perhapsRebroadcast(const meshtastic_MeshPacket *p) = 0;
|
||||
|
||||
/* Check if we should handle an upgraded packet (with higher hop_limit)
|
||||
* @return true if we handled it (so stop processing)
|
||||
*/
|
||||
bool perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p);
|
||||
|
||||
/* Call when we receive a packet that needs some reprocessing, but afterwards should be filtered */
|
||||
void reprocessPacket(const meshtastic_MeshPacket *p);
|
||||
|
||||
// Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of
|
||||
// the same packet
|
||||
bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -65,5 +65,7 @@ template <class T> class LR11x0Interface : public RadioLibInterface
|
||||
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
|
||||
|
||||
virtual void setStandby() override;
|
||||
|
||||
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
|
||||
};
|
||||
#endif
|
||||
@@ -65,7 +65,7 @@ void fixPriority(meshtastic_MeshPacket *p)
|
||||
}
|
||||
|
||||
/** enqueue a packet, return false if full */
|
||||
bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p)
|
||||
bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p, bool *dropped)
|
||||
{
|
||||
// no space - try to replace a lower priority packet in the queue
|
||||
if (queue.size() >= maxLen) {
|
||||
@@ -73,9 +73,16 @@ bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p)
|
||||
if (!replaced) {
|
||||
LOG_WARN("TX queue is full, and there is no lower-priority packet available to evict in favour of 0x%08x", p->id);
|
||||
}
|
||||
if (dropped) {
|
||||
*dropped = true;
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
if (dropped) {
|
||||
*dropped = false;
|
||||
}
|
||||
|
||||
// Find the correct position using upper_bound to maintain a stable order
|
||||
auto it = std::upper_bound(queue.begin(), queue.end(), p, CompareMeshPacketFunc);
|
||||
queue.insert(it, p); // Insert packet at the found position
|
||||
|
||||
@@ -19,8 +19,10 @@ class MeshPacketQueue
|
||||
public:
|
||||
explicit MeshPacketQueue(size_t _maxLen);
|
||||
|
||||
/** enqueue a packet, return false if full */
|
||||
bool enqueue(meshtastic_MeshPacket *p);
|
||||
/** enqueue a packet, return false if full
|
||||
* @param dropped Optional pointer to a bool that will be set to true if a packet was dropped
|
||||
*/
|
||||
bool enqueue(meshtastic_MeshPacket *p, bool *dropped = nullptr);
|
||||
|
||||
/** return true if the queue is empty */
|
||||
bool empty();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -256,6 +256,8 @@ NodeDB::NodeDB()
|
||||
owner.role = config.device.role;
|
||||
// Ensure macaddr is set to our macaddr as it will be copied in our info below
|
||||
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
|
||||
// Ensure owner.id is always derived from the node number
|
||||
snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum());
|
||||
|
||||
if (!config.has_security) {
|
||||
config.has_security = true;
|
||||
@@ -716,6 +718,12 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
strncpy(config.network.wifi_psk, USERPREFS_NETWORK_WIFI_PSK, sizeof(config.network.wifi_psk));
|
||||
#endif
|
||||
|
||||
#if defined(USERPREFS_NETWORK_IPV6_ENABLED)
|
||||
config.network.ipv6_enabled = USERPREFS_NETWORK_IPV6_ENABLED;
|
||||
#else
|
||||
config.network.ipv6_enabled = default_network_ipv6_enabled;
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_FLIP_SCREEN
|
||||
config.display.flip_screen = true;
|
||||
#endif
|
||||
@@ -1152,6 +1160,20 @@ void NodeDB::loadFromDisk()
|
||||
spiLock->unlock();
|
||||
#endif
|
||||
#ifdef FSCom
|
||||
#ifdef FACTORY_INSTALL
|
||||
spiLock->lock();
|
||||
if (!FSCom.exists("/prefs/" xstr(BUILD_EPOCH))) {
|
||||
LOG_WARN("Factory Install Reset!");
|
||||
FSCom.format();
|
||||
FSCom.mkdir("/prefs");
|
||||
File f2 = FSCom.open("/prefs/" xstr(BUILD_EPOCH), FILE_O_WRITE);
|
||||
if (f2) {
|
||||
f2.flush();
|
||||
f2.close();
|
||||
}
|
||||
}
|
||||
spiLock->unlock();
|
||||
#endif
|
||||
spiLock->lock();
|
||||
if (FSCom.exists(legacyPrefFileName)) {
|
||||
spiLock->unlock();
|
||||
@@ -1597,9 +1619,18 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
|
||||
void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
{
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
|
||||
if (!info) {
|
||||
if (!info || !contact.has_user) {
|
||||
return;
|
||||
}
|
||||
// If the local node has this node marked as manually verified
|
||||
// and the client does not, do not allow the client to update the
|
||||
// saved public key.
|
||||
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
|
||||
if (contact.user.public_key.size != info->user.public_key.size ||
|
||||
memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
info->num = contact.node_num;
|
||||
info->has_user = true;
|
||||
info->user = TypeConversions::ConvertToUserLite(contact.user);
|
||||
@@ -1614,10 +1645,12 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
} else {
|
||||
info->last_heard = getValidTime(RTCQualityNTP);
|
||||
info->is_favorite = true;
|
||||
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
||||
// As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
|
||||
if (contact.manually_verified) {
|
||||
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
||||
}
|
||||
// Mark the node's key as manually verified to indicate trustworthiness.
|
||||
updateGUIforNode = info;
|
||||
// powerFSM.trigger(EVENT_NODEDB_UPDATED); This event has been retired
|
||||
sortMeshDB();
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
}
|
||||
@@ -1666,6 +1699,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
||||
}
|
||||
#endif
|
||||
|
||||
// Always ensure user.id is derived from nodeId, regardless of what was received
|
||||
snprintf(p.id, sizeof(p.id), "!%08x", nodeId);
|
||||
|
||||
// Both of info->user and p start as filled with zero so I think this is okay
|
||||
auto lite = TypeConversions::ConvertToUserLite(p);
|
||||
bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex);
|
||||
@@ -1844,6 +1880,13 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
|
||||
return info->channel;
|
||||
}
|
||||
|
||||
std::string NodeDB::getNodeId() const
|
||||
{
|
||||
char nodeId[16];
|
||||
snprintf(nodeId, sizeof(nodeId), "!%08x", myNodeInfo.my_node_num);
|
||||
return std::string(nodeId);
|
||||
}
|
||||
|
||||
/// Find a node in our DB, return null for missing
|
||||
/// NOTE: This function might be called from an ISR
|
||||
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <pb_encode.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "MeshTypes.h"
|
||||
@@ -203,6 +204,9 @@ class NodeDB
|
||||
/// @return our node number
|
||||
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
|
||||
|
||||
/// @return our node ID as a string in the format "!xxxxxxxx"
|
||||
std::string getNodeId() const;
|
||||
|
||||
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
|
||||
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }
|
||||
|
||||
|
||||
253
src/mesh/PacketCache.cpp
Normal file
253
src/mesh/PacketCache.cpp
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/mesh/PacketCache.h
Normal file
75
src/mesh/PacketCache.h
Normal file
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,8 +74,13 @@ 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();
|
||||
}
|
||||
|
||||
@@ -93,7 +102,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 +162,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 +257,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,16 +456,44 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
break;
|
||||
|
||||
case STATE_SEND_OTHER_NODEINFOS: {
|
||||
LOG_DEBUG("Send known nodes");
|
||||
if (nodeInfoForPhone.num != 0) {
|
||||
LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
|
||||
nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
|
||||
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(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);
|
||||
@@ -520,11 +573,15 @@ 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;
|
||||
|
||||
// Allow subclasses to know we've entered steady-state so they can lower power consumption
|
||||
onConfigComplete();
|
||||
|
||||
pauseBluetoothLogging = false;
|
||||
}
|
||||
|
||||
@@ -544,6 +601,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) {
|
||||
@@ -579,21 +663,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();
|
||||
@@ -710,6 +790,13 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
|
||||
// sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Text messages can only be sent once every 2 seconds");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upgrade traceroute requests from phone to use reliable delivery, matching TraceRouteModule
|
||||
if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && !isBroadcast(p.to)) {
|
||||
// Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)
|
||||
p.want_ack = true;
|
||||
}
|
||||
|
||||
lastPortNumToRadio[p.decoded.portnum] = millis();
|
||||
service->handleToRadio(p);
|
||||
return true;
|
||||
@@ -725,7 +812,7 @@ int PhoneAPI::onNotify(uint32_t newValue)
|
||||
LOG_INFO("Tell client we have new packets %u", newValue);
|
||||
onNowHasData(newValue);
|
||||
} else {
|
||||
LOG_DEBUG("(Client not yet interested in packets)");
|
||||
LOG_DEBUG("Client not yet interested in packets (state=%d)", state);
|
||||
}
|
||||
|
||||
return timeout ? -1 : 0; // If we timed out, MeshService should stop iterating through observers as we just removed one
|
||||
|
||||
@@ -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,6 +159,11 @@ 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();
|
||||
|
||||
@@ -158,6 +172,8 @@ class PhoneAPI
|
||||
|
||||
void releaseQueueStatusPhonePacket();
|
||||
|
||||
void prefetchNodeInfos();
|
||||
|
||||
void releaseMqttClientProxyPhonePacket();
|
||||
|
||||
void releaseClientNotification();
|
||||
|
||||
@@ -13,7 +13,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
||||
const pb_msgdesc_t *fields;
|
||||
|
||||
public:
|
||||
uint8_t numOnlineNodes = 0;
|
||||
uint16_t numOnlineNodes = 0;
|
||||
/** Constructor
|
||||
* name is for debugging output
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -65,8 +65,10 @@ class RF95Interface : public RadioLibInterface
|
||||
*/
|
||||
virtual void configHardwareForSend() override;
|
||||
|
||||
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(*lora, pl, received); }
|
||||
|
||||
private:
|
||||
/** Some boards require GPIO control of tx vs rx paths */
|
||||
void setTransmitEnable(bool txon);
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -230,33 +230,7 @@ The band is from 902 to 928 MHz. It mentions channel number and its respective c
|
||||
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculate airtime per
|
||||
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
|
||||
* section 4
|
||||
*
|
||||
* @return num msecs for the packet
|
||||
*/
|
||||
uint32_t RadioInterface::getPacketTime(uint32_t pl)
|
||||
{
|
||||
float bandwidthHz = bw * 1000.0f;
|
||||
bool headDisable = false; // we currently always use the header
|
||||
float tSym = (1 << sf) / bandwidthHz;
|
||||
|
||||
bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
|
||||
|
||||
float tPreamble = (preambleLength + 4.25f) * tSym;
|
||||
float numPayloadSym =
|
||||
8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
|
||||
float tPayload = numPayloadSym * tSym;
|
||||
float tPacket = tPreamble + tPayload;
|
||||
|
||||
uint32_t msecs = tPacket * 1000;
|
||||
|
||||
return msecs;
|
||||
}
|
||||
|
||||
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p)
|
||||
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received)
|
||||
{
|
||||
uint32_t pl = 0;
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
|
||||
@@ -265,7 +239,7 @@ uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p)
|
||||
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
|
||||
pl = numbytes + sizeof(PacketHeader);
|
||||
}
|
||||
return getPacketTime(pl);
|
||||
return getPacketTime(pl, received);
|
||||
}
|
||||
|
||||
/** The delay to use for retransmitting dropped packets */
|
||||
@@ -298,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);
|
||||
}
|
||||
@@ -624,8 +598,7 @@ void RadioInterface::applyModemConfig()
|
||||
saveFreq(freq + loraConfig.frequency_offset);
|
||||
|
||||
slotTimeMsec = computeSlotTimeMsec();
|
||||
preambleTimeMsec = getPacketTime((uint32_t)0);
|
||||
maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
|
||||
preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
|
||||
|
||||
LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset);
|
||||
LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset,
|
||||
@@ -635,7 +608,7 @@ void RadioInterface::applyModemConfig()
|
||||
LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw);
|
||||
LOG_INFO("channel_num: %d", channel_num + 1);
|
||||
LOG_INFO("frequency: %f", getFreq());
|
||||
LOG_INFO("Slot time: %u msec", slotTimeMsec);
|
||||
LOG_INFO("Slot time: %u msec, preamble time: %u msec", slotTimeMsec, preambleTimeMsec);
|
||||
}
|
||||
|
||||
/** Slottime is the time to detect a transmission has started, consisting of:
|
||||
@@ -673,11 +646,26 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
power = maxPower;
|
||||
}
|
||||
|
||||
if (TX_GAIN_LORA > 0) {
|
||||
#ifndef NUM_PA_POINTS
|
||||
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
|
||||
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;
|
||||
|
||||
|
||||
@@ -87,9 +87,8 @@ class RadioInterface
|
||||
const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
|
||||
const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
|
||||
uint32_t slotTimeMsec = computeSlotTimeMsec();
|
||||
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
|
||||
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
|
||||
uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast
|
||||
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
|
||||
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
|
||||
const uint32_t PROCESSING_TIME_MSEC =
|
||||
4500; // time to construct, process and construct a packet again (empirically determined)
|
||||
const uint8_t CWmin = 3; // minimum CWsize
|
||||
@@ -202,8 +201,8 @@ class RadioInterface
|
||||
*
|
||||
* @return num msecs for the packet
|
||||
*/
|
||||
uint32_t getPacketTime(const meshtastic_MeshPacket *p);
|
||||
uint32_t getPacketTime(uint32_t totalPacketLen);
|
||||
uint32_t getPacketTime(const meshtastic_MeshPacket *p, bool received = false);
|
||||
virtual uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) = 0;
|
||||
|
||||
/**
|
||||
* Get the channel we saved.
|
||||
|
||||
@@ -116,16 +116,21 @@ bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidF
|
||||
if (detected) {
|
||||
if (!activeReceiveStart) {
|
||||
activeReceiveStart = millis();
|
||||
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && !(irq & syncWordHeaderValidFlag)) {
|
||||
// The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
|
||||
activeReceiveStart = 0;
|
||||
LOG_DEBUG("Ignore false preamble detection");
|
||||
return false;
|
||||
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
|
||||
// We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
|
||||
activeReceiveStart = 0;
|
||||
LOG_DEBUG("Ignore false header detection");
|
||||
return false;
|
||||
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec)) {
|
||||
if (!(irq & syncWordHeaderValidFlag)) {
|
||||
// The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
|
||||
activeReceiveStart = 0;
|
||||
LOG_DEBUG("Ignore false preamble detection");
|
||||
return false;
|
||||
} else {
|
||||
uint32_t maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
|
||||
if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
|
||||
// We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
|
||||
activeReceiveStart = 0;
|
||||
LOG_DEBUG("Ignore false header detection");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return detected;
|
||||
@@ -172,7 +177,12 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p)
|
||||
printPacket("enqueue for send", p);
|
||||
|
||||
LOG_DEBUG("txGood=%d,txRelay=%d,rxGood=%d,rxBad=%d", txGood, txRelay, rxGood, rxBad);
|
||||
ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN;
|
||||
bool dropped = false;
|
||||
ErrorCode res = txQueue.enqueue(p, &dropped) ? ERRNO_OK : ERRNO_UNKNOWN;
|
||||
|
||||
if (dropped) {
|
||||
txDrop++;
|
||||
}
|
||||
|
||||
if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
|
||||
packetPool.release(p);
|
||||
@@ -279,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());
|
||||
}
|
||||
}
|
||||
@@ -354,11 +359,15 @@ void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id)
|
||||
meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false);
|
||||
if (p) {
|
||||
p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr);
|
||||
if (txQueue.enqueue(p)) {
|
||||
bool dropped = false;
|
||||
if (txQueue.enqueue(p, &dropped)) {
|
||||
LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
|
||||
} else {
|
||||
packetPool.release(p);
|
||||
}
|
||||
if (dropped) {
|
||||
txDrop++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,6 +408,10 @@ void RadioLibInterface::completeSending()
|
||||
sendingPacket = NULL;
|
||||
|
||||
if (p) {
|
||||
// Packet has been sent, count it toward our TX airtime utilization.
|
||||
uint32_t xmitMsec = getPacketTime(p);
|
||||
airTime->logAirtime(TX_LOG, xmitMsec);
|
||||
|
||||
txGood++;
|
||||
if (!isFromUs(p))
|
||||
txRelay++;
|
||||
@@ -411,8 +424,6 @@ void RadioLibInterface::completeSending()
|
||||
|
||||
void RadioLibInterface::handleReceiveInterrupt()
|
||||
{
|
||||
uint32_t xmitMsec;
|
||||
|
||||
// when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race
|
||||
// Condition?
|
||||
if (!isReceiving) {
|
||||
@@ -425,12 +436,12 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
// read the number of actually received bytes
|
||||
size_t length = iface->getPacketLength();
|
||||
|
||||
xmitMsec = getPacketTime(length);
|
||||
uint32_t rxMsec = getPacketTime(length, true);
|
||||
|
||||
#ifndef DISABLE_WELCOME_UNSET
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||
LOG_WARN("lora rx disabled: Region unset");
|
||||
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
|
||||
airTime->logAirtime(RX_ALL_LOG, rxMsec);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -446,7 +457,7 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
|
||||
rxBad++;
|
||||
|
||||
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
|
||||
airTime->logAirtime(RX_ALL_LOG, rxMsec);
|
||||
|
||||
} else {
|
||||
// Skip the 4 headers that are at the beginning of the rxBuf
|
||||
@@ -456,7 +467,7 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
if (payloadLen < 0) {
|
||||
LOG_WARN("Ignore received packet too short");
|
||||
rxBad++;
|
||||
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
|
||||
airTime->logAirtime(RX_ALL_LOG, rxMsec);
|
||||
} else {
|
||||
rxGood++;
|
||||
// altered packet with "from == 0" can do Remote Node Administration without permission
|
||||
@@ -494,7 +505,7 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
|
||||
printPacket("Lora RX", mp);
|
||||
|
||||
airTime->logAirtime(RX_LOG, xmitMsec);
|
||||
airTime->logAirtime(RX_LOG, rxMsec);
|
||||
|
||||
deliverToReceiver(mp);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE);
|
||||
|
||||
protected:
|
||||
ModemType_t modemType = RADIOLIB_MODEM_LORA;
|
||||
DataRate_t getDataRate() const { return {.lora = {.spreadingFactor = sf, .bandwidth = bw, .codingRate = cr}}; }
|
||||
PacketConfig_t getPacketConfig() const
|
||||
{
|
||||
return {.lora = {.preambleLength = preambleLength,
|
||||
.implicitHeader = false,
|
||||
.crcEnabled = true,
|
||||
// We use auto LDRO, meaning it is enabled if the symbol time is >= 16msec
|
||||
.ldrOptimize = (1 << sf) / bw >= 16}};
|
||||
}
|
||||
|
||||
/**
|
||||
* We use a meshtastic sync word, but hashed with the Channel name. For releases before 1.2 we used 0x12 (or for very old
|
||||
* loads 0x14) Note: do not use 0x34 - that is reserved for lorawan
|
||||
@@ -105,6 +116,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
* Debugging counts
|
||||
*/
|
||||
uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0;
|
||||
uint16_t txDrop = 0;
|
||||
|
||||
public:
|
||||
RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
|
||||
@@ -209,6 +221,36 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
*/
|
||||
virtual void setStandby();
|
||||
|
||||
/**
|
||||
* Derive packet time either for a received (using header info) or a transmitted packet
|
||||
*/
|
||||
template <typename T> uint32_t computePacketTime(T &lora, uint32_t pl, bool received)
|
||||
{
|
||||
if (received) {
|
||||
// First get the actual coding rate and CRC status from the received packet
|
||||
uint8_t rxCR;
|
||||
bool hasCRC;
|
||||
lora.getLoRaRxHeaderInfo(&rxCR, &hasCRC);
|
||||
// Go from raw header value to denominator
|
||||
if (rxCR < 5) {
|
||||
rxCR += 4;
|
||||
} else if (rxCR == 7) {
|
||||
rxCR = 8;
|
||||
}
|
||||
|
||||
// Received packet configuration must be the same as configured, except for coding rate and CRC
|
||||
DataRate_t dr = getDataRate();
|
||||
dr.lora.codingRate = rxCR;
|
||||
|
||||
PacketConfig_t pc = getPacketConfig();
|
||||
pc.lora.crcEnabled = hasCRC;
|
||||
|
||||
return lora.calculateTimeOnAir(modemType, dr, pc, pl) / 1000;
|
||||
}
|
||||
|
||||
return lora.getTimeOnAir(pl) / 1000;
|
||||
}
|
||||
|
||||
const char *radioLibErr = "RadioLib err=";
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,7 +76,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
If we don't add this, we will likely retransmit too early.
|
||||
*/
|
||||
for (auto i = pending.begin(); i != pending.end(); i++) {
|
||||
i->second.nextTxMsec += iface->getPacketTime(p);
|
||||
i->second.nextTxMsec += iface->getPacketTime(p, true);
|
||||
}
|
||||
|
||||
return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p);
|
||||
@@ -103,10 +103,20 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
||||
/* A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received
|
||||
an implicit ACK already. If we received it directly or via NextHopRouter, only ACK with a hop limit of 0 to
|
||||
make sure the other side stops retransmitting. */
|
||||
if (!p->decoded.request_id && !p->decoded.reply_id) {
|
||||
|
||||
if (shouldSuccessAckWithWantAck(p)) {
|
||||
// If this packet should always be ACKed reliably with want_ack back to the original sender, make sure we
|
||||
// do that unconditionally.
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
|
||||
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit), true);
|
||||
} else if (!p->decoded.request_id && !p->decoded.reply_id) {
|
||||
// If it's not an ACK or a reply, send an ACK.
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
|
||||
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
||||
} else if ((p->hop_start > 0 && p->hop_start == p->hop_limit) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
|
||||
// If we received the packet directly from the original sender, send a 0-hop ACK since the original sender
|
||||
// won't overhear any implicit ACKs. If we received the packet via NextHopRouter, also send a 0-hop ACK to
|
||||
// stop the immediate relayer's retransmissions.
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
||||
}
|
||||
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
|
||||
@@ -152,4 +162,36 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
||||
|
||||
// handle the packet as normal
|
||||
isBroadcast(p->to) ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* If we ACK this packet, should we set want_ack=true on the ACK for reliable delivery of the ACK packet?
|
||||
*/
|
||||
bool ReliableRouter::shouldSuccessAckWithWantAck(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
// Don't ACK-with-want-ACK outgoing packets
|
||||
if (isFromUs(p))
|
||||
return false;
|
||||
|
||||
// Only ACK-with-want-ACK if the original packet asked for want_ack
|
||||
if (!p->want_ack)
|
||||
return false;
|
||||
|
||||
// Only ACK-with-want-ACK packets to us (not broadcast)
|
||||
if (!isToUs(p))
|
||||
return false;
|
||||
|
||||
// Special case for text message DMs:
|
||||
bool isTextMessage =
|
||||
(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
|
||||
IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_TEXT_MESSAGE_APP, meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP);
|
||||
|
||||
if (isTextMessage) {
|
||||
// If it's a non-broadcast text message, and the original asked for want_ack,
|
||||
// let's send an ACK that is itself want_ack to improve reliability of confirming delivery back to the sender.
|
||||
// This should include all DMs regardless of whether or not reply_id is set.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -31,4 +31,10 @@ class ReliableRouter : public NextHopRouter
|
||||
* We hook this method so we can see packets before FloodingRouter says they should be discarded
|
||||
*/
|
||||
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Should this packet be ACKed with a want_ack for reliable delivery?
|
||||
*/
|
||||
bool shouldSuccessAckWithWantAck(const meshtastic_MeshPacket *p);
|
||||
};
|
||||
@@ -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
|
||||
@@ -198,9 +207,10 @@ meshtastic_MeshPacket *Router::allocForSending()
|
||||
/**
|
||||
* Send an ack or a nak packet back towards whoever sent idFrom
|
||||
*/
|
||||
void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit)
|
||||
void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit,
|
||||
bool ackWantsAck)
|
||||
{
|
||||
routingModule->sendAckNak(err, to, idFrom, chIndex, hopLimit);
|
||||
routingModule->sendAckNak(err, to, idFrom, chIndex, hopLimit, ackWantsAck);
|
||||
}
|
||||
|
||||
void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p)
|
||||
|
||||
@@ -125,7 +125,8 @@ class Router : protected concurrency::OSThread, protected PacketHistory
|
||||
/**
|
||||
* Send an ack or a nak packet back towards whoever sent idFrom
|
||||
*/
|
||||
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0);
|
||||
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
|
||||
bool ackWantsAck = false);
|
||||
|
||||
private:
|
||||
/**
|
||||
|
||||
@@ -52,7 +52,7 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#ifdef HELTEC_V4
|
||||
#if defined(USE_GC1109_PA)
|
||||
pinMode(LORA_PA_POWER, OUTPUT);
|
||||
digitalWrite(LORA_PA_POWER, HIGH);
|
||||
|
||||
@@ -80,6 +80,9 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
RadioLibInterface::init();
|
||||
|
||||
limitPower(SX126X_MAX_POWER);
|
||||
// Make sure we reach the minimum power supported to turn the chip on (-9dBm)
|
||||
if (power < -9)
|
||||
power = -9;
|
||||
|
||||
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
|
||||
// \todo Display actual typename of the adapter, not just `SX126x`
|
||||
@@ -118,8 +121,8 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
LOG_DEBUG("Set DIO2 as %sRF switch, result: %d", dio2AsRfSwitch ? "" : "not ", res);
|
||||
}
|
||||
|
||||
// If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has
|
||||
// no effect
|
||||
// If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has
|
||||
// no effect
|
||||
#if ARCH_PORTDUINO
|
||||
if (res == RADIOLIB_ERR_NONE) {
|
||||
LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", portduino_config.lora_rxen_pin.pin,
|
||||
@@ -263,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.
|
||||
@@ -349,7 +353,7 @@ template <typename T> bool SX126xInterface<T>::sleep()
|
||||
digitalWrite(SX126X_POWER_EN, LOW);
|
||||
#endif
|
||||
|
||||
#ifdef HELTEC_V4
|
||||
#if defined(USE_GC1109_PA)
|
||||
/*
|
||||
* Do not switch the power on and off frequently.
|
||||
* After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
|
||||
@@ -364,7 +368,7 @@ template <typename T> bool SX126xInterface<T>::sleep()
|
||||
/** Some boards require GPIO control of tx vs rx paths */
|
||||
template <typename T> void SX126xInterface<T>::setTransmitEnable(bool txon)
|
||||
{
|
||||
#ifdef HELTEC_V4
|
||||
#if defined(USE_GC1109_PA)
|
||||
digitalWrite(LORA_PA_POWER, HIGH);
|
||||
digitalWrite(LORA_PA_EN, HIGH);
|
||||
digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);
|
||||
|
||||
@@ -72,6 +72,8 @@ template <class T> class SX126xInterface : public RadioLibInterface
|
||||
|
||||
virtual void setStandby() override;
|
||||
|
||||
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
|
||||
|
||||
private:
|
||||
/** Some boards require GPIO control of tx vs rx paths */
|
||||
void setTransmitEnable(bool txon);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user