mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-14 23:02:53 +00:00
Compare commits
165 Commits
b-kind-don
...
arduino-es
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b05f090dc | ||
|
|
e9b87f7a81 | ||
|
|
173b75a1c0 | ||
|
|
b0dae54c97 | ||
|
|
71d84404c6 | ||
|
|
6f5bdd73cb | ||
|
|
cc3c568501 | ||
|
|
13ebceb3bc | ||
|
|
70724bef72 | ||
|
|
bf4e2e8e86 | ||
|
|
2dc7760508 | ||
|
|
d201f6a1ed | ||
|
|
9977035499 | ||
|
|
096afa07f8 | ||
|
|
760471d620 | ||
|
|
6165b4f7a9 | ||
|
|
ae814b5463 | ||
|
|
4ee07226e4 | ||
|
|
78dfb05eeb | ||
|
|
9211b1bb4b | ||
|
|
70ac3601b0 | ||
|
|
51acd92a37 | ||
|
|
6d2093650a | ||
|
|
b6dd99917d | ||
|
|
d00b2afe1d | ||
|
|
e49b07ac8c | ||
|
|
8989de118c | ||
|
|
1914fa0321 | ||
|
|
d47d249550 | ||
|
|
962e5d513c | ||
|
|
ac4bcd2f56 | ||
|
|
e17c50bb86 | ||
|
|
abc0eb196a | ||
|
|
701028b749 | ||
|
|
108bdf7b0d | ||
|
|
f267b5f5f7 | ||
|
|
0cd860e300 | ||
|
|
31fdb36987 | ||
|
|
e7741c20e4 | ||
|
|
d1d16fc25f | ||
|
|
c8afbe68b5 | ||
|
|
803e96800e | ||
|
|
6c69780615 | ||
|
|
ff28355e16 | ||
|
|
d5bb566276 | ||
|
|
39ff880506 | ||
|
|
209157c9dd | ||
|
|
fb59d68edd | ||
|
|
351cbd723b | ||
|
|
7b854fb5ca | ||
|
|
f8b160595f | ||
|
|
37d14f942e | ||
|
|
4594ae474e | ||
|
|
f26e657577 | ||
|
|
a25bfd264c | ||
|
|
ec9f3fa6ea | ||
|
|
8356ad97e4 | ||
|
|
bf51c38975 | ||
|
|
3df3c876cc | ||
|
|
68f07c5f9d | ||
|
|
7fb96ce2ba | ||
|
|
12687a1073 | ||
|
|
89de499198 | ||
|
|
4881362340 | ||
|
|
18000ccf21 | ||
|
|
7776ec15b6 | ||
|
|
e4c7fca716 | ||
|
|
5b63bd9331 | ||
|
|
289f90bdbe | ||
|
|
09a0df3a1f | ||
|
|
fe329892de | ||
|
|
2681332678 | ||
|
|
f994eb185f | ||
|
|
55c23dec13 | ||
|
|
4dfc062abd | ||
|
|
0be21d90c1 | ||
|
|
a0c0388dd9 | ||
|
|
e8367894f2 | ||
|
|
8aae4f1b9d | ||
|
|
8a8f60d129 | ||
|
|
b59409bec0 | ||
|
|
c66125114f | ||
|
|
edb7ec58c6 | ||
|
|
655c6b51fe | ||
|
|
0bd4cefad3 | ||
|
|
9b1fb795d7 | ||
|
|
3040e5a7bb | ||
|
|
3b82d55176 | ||
|
|
a6b8202cd4 | ||
|
|
cfc1bf10c9 | ||
|
|
7d1300ab66 | ||
|
|
bd3cbfc1ad | ||
|
|
fddc4e00ca | ||
|
|
5f7eec5504 | ||
|
|
6b94c297b9 | ||
|
|
edeb25cab5 | ||
|
|
44688e8363 | ||
|
|
5ae4ff9162 | ||
|
|
ed394f5f9d | ||
|
|
11db6d4dcc | ||
|
|
4e03df5ea7 | ||
|
|
d3e3a91096 | ||
|
|
b0e8321514 | ||
|
|
6c7cff7de2 | ||
|
|
834c3c5cc2 | ||
|
|
25a19b49ad | ||
|
|
a4d96bebfb | ||
|
|
d21d6d2085 | ||
|
|
26c38ffc8e | ||
|
|
237b8908f7 | ||
|
|
06bccef462 | ||
|
|
3120bb8fd7 | ||
|
|
0903ed8232 | ||
|
|
f8ba392a24 | ||
|
|
3dd384dd53 | ||
|
|
2c071a3283 | ||
|
|
596cd7e0b6 | ||
|
|
3f5c30e3b3 | ||
|
|
1a279c6053 | ||
|
|
3d825c51dd | ||
|
|
915f882e1f | ||
|
|
18d005d7e6 | ||
|
|
8791cd7851 | ||
|
|
590db89643 | ||
|
|
ea1d968777 | ||
|
|
40d728a14b | ||
|
|
e39b56547e | ||
|
|
a7f63d5783 | ||
|
|
5136c8ba24 | ||
|
|
8b42bf7a95 | ||
|
|
be1a724ab4 | ||
|
|
04f98cf20e | ||
|
|
75a1a41d8c | ||
|
|
344efa6675 | ||
|
|
e919b3da9c | ||
|
|
155783c782 | ||
|
|
76bcd05259 | ||
|
|
2d29cbc34c | ||
|
|
2b77864192 | ||
|
|
10aa4cbfee | ||
|
|
c24a174490 | ||
|
|
3e5e19efec | ||
|
|
ea2c247024 | ||
|
|
0b463b6443 | ||
|
|
7d3c804279 | ||
|
|
1fa7d9f40e | ||
|
|
aa7abb1d3e | ||
|
|
effb454af0 | ||
|
|
27bb42ed3b | ||
|
|
05dc8caed8 | ||
|
|
f3734d407d | ||
|
|
bc8e509a8c | ||
|
|
74d0472834 | ||
|
|
5afc43ebc1 | ||
|
|
6cdb92b7a8 | ||
|
|
0e1872398a | ||
|
|
f25544c7e0 | ||
|
|
f40b2ba153 | ||
|
|
5033fd1f9f | ||
|
|
cd170fb011 | ||
|
|
fa169a5e43 | ||
|
|
c6e2a53a02 | ||
|
|
a286c06271 | ||
|
|
dda4b90e34 | ||
|
|
cc5dc5046c |
7
.github/actions/setup-base/action.yml
vendored
7
.github/actions/setup-base/action.yml
vendored
@@ -11,11 +11,6 @@ runs:
|
|||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
- name: Uncomment build epoch
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -23,7 +18,7 @@ runs:
|
|||||||
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
|
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|||||||
11
.github/workflows/main_matrix.yml
vendored
11
.github/workflows/main_matrix.yml
vendored
@@ -3,7 +3,7 @@ concurrency:
|
|||||||
group: ci-${{ github.head_ref || github.run_id }}
|
group: ci-${{ github.head_ref || github.run_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
on:
|
on:
|
||||||
# # Triggers the workflow on push but only for the master branch
|
# # Triggers the workflow on push but only for the main branches
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
cache: pip
|
cache: pip
|
||||||
@@ -258,6 +258,7 @@ jobs:
|
|||||||
push: false
|
push: false
|
||||||
|
|
||||||
gather-artifacts:
|
gather-artifacts:
|
||||||
|
# trunk-ignore(checkov/CKV2_GHA_1)
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
@@ -370,7 +371,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
@@ -439,7 +440,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
@@ -494,7 +495,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
|
|||||||
508
.github/workflows/merge_queue.yml
vendored
Normal file
508
.github/workflows/merge_queue.yml
vendored
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
name: Merge Queue
|
||||||
|
# Not sure how concurrency works in merge_queue, removing for now.
|
||||||
|
# concurrency:
|
||||||
|
# group: merge-queue-${{ github.head_ref || github.run_id }}
|
||||||
|
# cancel-in-progress: true
|
||||||
|
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
|
||||||
|
- check
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
cache: pip
|
||||||
|
- run: pip install -U platformio
|
||||||
|
- 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)
|
||||||
|
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
|
||||||
|
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 }}
|
||||||
|
|
||||||
|
version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Get release version string
|
||||||
|
run: |
|
||||||
|
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
|
echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
|
||||||
|
id: version
|
||||||
|
env:
|
||||||
|
BUILD_LOCATION: local
|
||||||
|
outputs:
|
||||||
|
long: ${{ steps.version.outputs.long }}
|
||||||
|
deb: ${{ steps.version.outputs.deb }}
|
||||||
|
|
||||||
|
check:
|
||||||
|
needs: setup
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix: ${{ fromJson(needs.setup.outputs.check) }}
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build base
|
||||||
|
id: base
|
||||||
|
uses: ./.github/actions/setup-base
|
||||||
|
- name: Check ${{ matrix.board }}
|
||||||
|
run: bin/check-all.sh ${{ matrix.board }}
|
||||||
|
|
||||||
|
build-esp32:
|
||||||
|
needs: [setup, version]
|
||||||
|
strategy:
|
||||||
|
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||||
|
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
|
||||||
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
|
with:
|
||||||
|
version: ${{ needs.version.outputs.long }}
|
||||||
|
pio_env: ${{ matrix.board }}
|
||||||
|
platform: esp32
|
||||||
|
|
||||||
|
build-esp32s3:
|
||||||
|
needs: [setup, version]
|
||||||
|
strategy:
|
||||||
|
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||||
|
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
|
||||||
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
|
with:
|
||||||
|
version: ${{ needs.version.outputs.long }}
|
||||||
|
pio_env: ${{ matrix.board }}
|
||||||
|
platform: esp32s3
|
||||||
|
|
||||||
|
build-esp32c3:
|
||||||
|
needs: [setup, version]
|
||||||
|
strategy:
|
||||||
|
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||||
|
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
|
||||||
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
|
with:
|
||||||
|
version: ${{ needs.version.outputs.long }}
|
||||||
|
pio_env: ${{ matrix.board }}
|
||||||
|
platform: esp32c3
|
||||||
|
|
||||||
|
build-esp32c6:
|
||||||
|
needs: [setup, version]
|
||||||
|
strategy:
|
||||||
|
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||||
|
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
|
||||||
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
|
with:
|
||||||
|
version: ${{ needs.version.outputs.long }}
|
||||||
|
pio_env: ${{ matrix.board }}
|
||||||
|
platform: esp32c6
|
||||||
|
|
||||||
|
build-nrf52840:
|
||||||
|
needs: [setup, version]
|
||||||
|
strategy:
|
||||||
|
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||||
|
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
|
||||||
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
|
with:
|
||||||
|
version: ${{ needs.version.outputs.long }}
|
||||||
|
pio_env: ${{ matrix.board }}
|
||||||
|
platform: nrf52840
|
||||||
|
|
||||||
|
build-rp2040:
|
||||||
|
needs: [setup, version]
|
||||||
|
strategy:
|
||||||
|
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||||
|
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
|
||||||
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
|
with:
|
||||||
|
version: ${{ needs.version.outputs.long }}
|
||||||
|
pio_env: ${{ matrix.board }}
|
||||||
|
platform: rp2040
|
||||||
|
|
||||||
|
build-rp2350:
|
||||||
|
needs: [setup, version]
|
||||||
|
strategy:
|
||||||
|
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||||
|
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
|
||||||
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
|
with:
|
||||||
|
version: ${{ needs.version.outputs.long }}
|
||||||
|
pio_env: ${{ matrix.board }}
|
||||||
|
platform: rp2350
|
||||||
|
|
||||||
|
build-stm32:
|
||||||
|
needs: [setup, version]
|
||||||
|
strategy:
|
||||||
|
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
|
||||||
|
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
|
||||||
|
uses: ./.github/workflows/build_firmware.yml
|
||||||
|
with:
|
||||||
|
version: ${{ needs.version.outputs.long }}
|
||||||
|
pio_env: ${{ matrix.board }}
|
||||||
|
platform: stm32
|
||||||
|
|
||||||
|
build-debian-src:
|
||||||
|
if: github.repository == 'meshtastic/firmware'
|
||||||
|
uses: ./.github/workflows/build_debian_src.yml
|
||||||
|
with:
|
||||||
|
series: UNRELEASED
|
||||||
|
build_location: local
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
package-pio-deps-native-tft:
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||||
|
uses: ./.github/workflows/package_pio_deps.yml
|
||||||
|
with:
|
||||||
|
pio_env: native-tft
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
test-native:
|
||||||
|
if: ${{ !contains(github.ref_name, 'event/') }}
|
||||||
|
uses: ./.github/workflows/test_native.yml
|
||||||
|
|
||||||
|
docker-deb-amd64:
|
||||||
|
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
|
||||||
|
push: false
|
||||||
|
|
||||||
|
gather-artifacts:
|
||||||
|
# trunk-ignore(checkov/CKV2_GHA_1)
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch:
|
||||||
|
- esp32
|
||||||
|
- esp32s3
|
||||||
|
- esp32c3
|
||||||
|
- esp32c6
|
||||||
|
- nrf52840
|
||||||
|
- rp2040
|
||||||
|
- rp2350
|
||||||
|
- stm32
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
[
|
||||||
|
version,
|
||||||
|
build-esp32,
|
||||||
|
build-esp32s3,
|
||||||
|
build-esp32c3,
|
||||||
|
build-esp32c6,
|
||||||
|
build-nrf52840,
|
||||||
|
build-rp2040,
|
||||||
|
build-rp2350,
|
||||||
|
build-stm32,
|
||||||
|
]
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./
|
||||||
|
pattern: firmware-${{matrix.arch}}-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Display structure of downloaded files
|
||||||
|
run: ls -R
|
||||||
|
|
||||||
|
- name: Move files up
|
||||||
|
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||||
|
|
||||||
|
- name: Repackage in single firmware zip
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
|
overwrite: true
|
||||||
|
path: |
|
||||||
|
./firmware-*.bin
|
||||||
|
./firmware-*.uf2
|
||||||
|
./firmware-*.hex
|
||||||
|
./firmware-*-ota.zip
|
||||||
|
./device-*.sh
|
||||||
|
./device-*.bat
|
||||||
|
./littlefs-*.bin
|
||||||
|
./bleota*bin
|
||||||
|
./Meshtastic_nRF52_factory_erase*.uf2
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
|
merge-multiple: true
|
||||||
|
path: ./output
|
||||||
|
|
||||||
|
# For diagnostics
|
||||||
|
- name: Show artifacts
|
||||||
|
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-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
|
- name: Repackage in single elfs zip
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||||
|
overwrite: true
|
||||||
|
path: ./*.elf
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- uses: scruplelesswizard/comment-artifact@main
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
with:
|
||||||
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
|
description: "Download firmware-${{matrix.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@v4
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
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@v4
|
||||||
|
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@v4
|
||||||
|
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@v4
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: firmware-${{matrix.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-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||||
|
merge-multiple: true
|
||||||
|
path: ./elfs
|
||||||
|
|
||||||
|
- name: Zip debug elfs
|
||||||
|
run: zip -j -9 -r ./debug-elfs-${{matrix.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-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||||
|
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{matrix.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@v4
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
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
|
||||||
2
.github/workflows/package_pio_deps.yml
vendored
2
.github/workflows/package_pio_deps.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/pr_enforce_labels.yml
vendored
2
.github/workflows/pr_enforce_labels.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Check for PR labels
|
- name: Check for PR labels
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const labels = context.payload.pull_request.labels.map(label => label.name);
|
const labels = context.payload.pull_request.labels.map(label => label.name);
|
||||||
|
|||||||
2
.github/workflows/pr_tests.yml
vendored
2
.github/workflows/pr_tests.yml
vendored
@@ -177,7 +177,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Comment test results on PR
|
- name: Comment test results on PR
|
||||||
if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped'
|
if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped'
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|||||||
2
.github/workflows/release_channels.yml
vendored
2
.github/workflows/release_channels.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/stale_bot.yml
vendored
2
.github/workflows/stale_bot.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Stale PR+Issues
|
- name: Stale PR+Issues
|
||||||
uses: actions/stale@v9.1.0
|
uses: actions/stale@v10.0.0
|
||||||
with:
|
with:
|
||||||
days-before-stale: 45
|
days-before-stale: 45
|
||||||
exempt-issue-labels: pinned,3.0
|
exempt-issue-labels: pinned,3.0
|
||||||
|
|||||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
pio upgrade
|
pio upgrade
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/trunk_format_pr.yml
vendored
2
.github/workflows/trunk_format_pr.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
git push
|
git push
|
||||||
|
|
||||||
- name: Comment on PR
|
- name: Comment on PR
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -39,5 +39,14 @@ release/
|
|||||||
src/mesh/raspihttp/certificate.pem
|
src/mesh/raspihttp/certificate.pem
|
||||||
src/mesh/raspihttp/private_key.pem
|
src/mesh/raspihttp/private_key.pem
|
||||||
|
|
||||||
|
# pioarduino platform
|
||||||
|
managed_components/*
|
||||||
|
arduino-lib-builder*
|
||||||
|
dependencies.lock
|
||||||
|
idf_component.yml
|
||||||
|
CMakeLists.txt
|
||||||
|
sdkconfig.*
|
||||||
|
.dummy/*
|
||||||
|
|
||||||
# Ignore logo (set at build time with platformio-custom.py)
|
# Ignore logo (set at build time with platformio-custom.py)
|
||||||
data/boot/logo.*
|
data/boot/logo.*
|
||||||
|
|||||||
@@ -4,28 +4,28 @@ cli:
|
|||||||
plugins:
|
plugins:
|
||||||
sources:
|
sources:
|
||||||
- id: trunk
|
- id: trunk
|
||||||
ref: v1.7.1
|
ref: v1.7.2
|
||||||
uri: https://github.com/trunk-io/plugins
|
uri: https://github.com/trunk-io/plugins
|
||||||
lint:
|
lint:
|
||||||
enabled:
|
enabled:
|
||||||
- checkov@3.2.461
|
- checkov@3.2.471
|
||||||
- renovate@41.74.0
|
- renovate@41.115.2
|
||||||
- prettier@3.6.2
|
- prettier@3.6.2
|
||||||
- trufflehog@3.90.5
|
- trufflehog@3.90.6
|
||||||
- yamllint@1.37.1
|
- yamllint@1.37.1
|
||||||
- bandit@1.8.6
|
- bandit@1.8.6
|
||||||
- trivy@0.64.1
|
- trivy@0.66.0
|
||||||
- taplo@0.9.3
|
- taplo@0.10.0
|
||||||
- ruff@0.12.7
|
- ruff@0.13.0
|
||||||
- isort@6.0.1
|
- isort@6.0.1
|
||||||
- markdownlint@0.45.0
|
- markdownlint@0.45.0
|
||||||
- oxipng@9.1.5
|
- oxipng@9.1.5
|
||||||
- svgo@4.0.0
|
- svgo@4.0.0
|
||||||
- actionlint@1.7.7
|
- actionlint@1.7.7
|
||||||
- flake8@7.3.0
|
- flake8@7.3.0
|
||||||
- hadolint@2.12.1-beta
|
- hadolint@2.13.1
|
||||||
- shfmt@3.6.0
|
- shfmt@3.6.0
|
||||||
- shellcheck@0.10.0
|
- shellcheck@0.11.0
|
||||||
- black@25.1.0
|
- black@25.1.0
|
||||||
- git-diff-check
|
- git-diff-check
|
||||||
- gitleaks@8.28.0
|
- gitleaks@8.28.0
|
||||||
|
|||||||
6
8MB_no_ota.csv
Normal file
6
8MB_no_ota.csv
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
|
otadata, data, ota, 0xe000, 0x2000,
|
||||||
|
app0, app, ota_0, 0x10000, 0x660000,
|
||||||
|
spiffs, data, spiffs, 0x670000,0x180000,
|
||||||
|
coredump, data, coredump,0x7F0000,0x10000,
|
||||||
|
@@ -52,8 +52,8 @@ lib_deps =
|
|||||||
https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip
|
https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip
|
||||||
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
|
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
|
||||||
h2zero/NimBLE-Arduino@^1.4.3
|
h2zero/NimBLE-Arduino@^1.4.3
|
||||||
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
|
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/mverch67/libpax gitBranch=master
|
||||||
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
|
https://github.com/mverch67/libpax/archive/6f52ee989301cdabaeef00bcbf93bff55708ce2f.zip
|
||||||
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
|
# 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.0.zip
|
||||||
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
[portduino_base]
|
[portduino_base]
|
||||||
platform =
|
platform =
|
||||||
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
|
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
|
||||||
https://github.com/meshtastic/platform-native/archive/37d986499ce24511952d7146db72d667c6bdaff7.zip
|
https://github.com/meshtastic/platform-native/archive/c490bcd019e0658404088a61b96e653c9da22c45.zip
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
@@ -31,6 +31,8 @@ lib_deps =
|
|||||||
https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
|
https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
|
||||||
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
|
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
|
||||||
adafruit/Adafruit seesaw Library@1.7.9
|
adafruit/Adafruit seesaw Library@1.7.9
|
||||||
|
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main
|
||||||
|
https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
${arduino_base.build_flags}
|
${arduino_base.build_flags}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ lib_deps =
|
|||||||
${radiolib_base.lib_deps}
|
${radiolib_base.lib_deps}
|
||||||
|
|
||||||
# renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main
|
# renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main
|
||||||
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip
|
https://github.com/caveman99/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
|
||||||
|
|
||||||
lib_ignore =
|
lib_ignore =
|
||||||
OneButton
|
OneButton
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
|
|
||||||
|
|
||||||
export PIP_BREAK_SYSTEM_PACKAGES=1
|
export PIP_BREAK_SYSTEM_PACKAGES=1
|
||||||
|
|
||||||
if (echo $2 | grep -q "esp32"); then
|
if (echo $2 | grep -q "esp32"); then
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ SET "DEBUG=0"
|
|||||||
SET "PYTHON="
|
SET "PYTHON="
|
||||||
SET "TFT_BUILD=0"
|
SET "TFT_BUILD=0"
|
||||||
SET "BIGDB8=0"
|
SET "BIGDB8=0"
|
||||||
|
SET "MUIDB8=0"
|
||||||
SET "BIGDB16=0"
|
SET "BIGDB16=0"
|
||||||
SET "ESPTOOL_BAUD=115200"
|
SET "ESPTOOL_BAUD=115200"
|
||||||
SET "ESPTOOL_CMD="
|
SET "ESPTOOL_CMD="
|
||||||
@@ -14,11 +15,12 @@ SET "LOGCOUNTER=0"
|
|||||||
SET "BPS_RESET=0"
|
SET "BPS_RESET=0"
|
||||||
|
|
||||||
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
||||||
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone"
|
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv"
|
||||||
SET "C3=esp32c3"
|
SET "C3=esp32c3"
|
||||||
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
|
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
|
||||||
SET "BIGDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
|
SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
|
||||||
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite t-watch-s3"
|
SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
|
||||||
|
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv"
|
||||||
|
|
||||||
GOTO getopts
|
GOTO getopts
|
||||||
:help
|
:help
|
||||||
@@ -100,7 +102,6 @@ IF NOT "!FILENAME:update=!"=="!FILENAME!" (
|
|||||||
)
|
)
|
||||||
|
|
||||||
:skip-filename
|
:skip-filename
|
||||||
SET "ESPTOOL_BAUD=1200"
|
|
||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
||||||
IF NOT "__%PYTHON%__"=="____" (
|
IF NOT "__%PYTHON%__"=="____" (
|
||||||
@@ -120,11 +121,10 @@ IF NOT "__%PYTHON%__"=="____" (
|
|||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
|
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
|
||||||
!ESPTOOL_CMD! >nul 2>&1
|
!ESPTOOL_CMD! >nul 2>&1
|
||||||
IF %ERRORLEVEL% GEQ 2 (
|
IF %ERRORLEVEL% EQU 9009 (
|
||||||
@REM esptool exits with code 1 if help is displayed.
|
@REM 9009 = command not found on Windows
|
||||||
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
|
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
|
||||||
EXIT /B 1
|
EXIT /B 1
|
||||||
GOTO eof
|
|
||||||
)
|
)
|
||||||
IF %DEBUG% EQU 1 (
|
IF %DEBUG% EQU 1 (
|
||||||
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
|
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
|
||||||
@@ -142,7 +142,7 @@ CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
|
|||||||
|
|
||||||
IF %BPS_RESET% EQU 1 (
|
IF %BPS_RESET% EQU 1 (
|
||||||
@REM Attempt to change mode via 1200bps Reset.
|
@REM Attempt to change mode via 1200bps Reset.
|
||||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
|
CALL :RUN_ESPTOOL 1200 --after no_reset read_flash_status
|
||||||
GOTO eof
|
GOTO eof
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -164,6 +164,15 @@ FOR %%a IN (%BIGDB_8MB%) DO (
|
|||||||
)
|
)
|
||||||
:end_loop_bigdb_8mb
|
:end_loop_bigdb_8mb
|
||||||
|
|
||||||
|
FOR %%a IN (%MUIDB_8MB%) DO (
|
||||||
|
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
|
||||||
|
@REM We are working with any of %MUIDB_8MB%.
|
||||||
|
SET "MUIDB8=1"
|
||||||
|
GOTO end_loop_muidb_8mb
|
||||||
|
)
|
||||||
|
)
|
||||||
|
:end_loop_muidb_8mb
|
||||||
|
|
||||||
FOR %%a IN (%BIGDB_16MB%) DO (
|
FOR %%a IN (%BIGDB_16MB%) DO (
|
||||||
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
|
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
|
||||||
@REM We are working with any of %BIGDB_16MB%.
|
@REM We are working with any of %BIGDB_16MB%.
|
||||||
@@ -174,6 +183,7 @@ FOR %%a IN (%BIGDB_16MB%) DO (
|
|||||||
:end_loop_bigdb_16mb
|
:end_loop_bigdb_16mb
|
||||||
|
|
||||||
IF %BIGDB8% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 8mb partition selected."
|
IF %BIGDB8% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 8mb partition selected."
|
||||||
|
IF %MUIDB8% EQU 1 CALL :LOG_MESSAGE INFO "MUIDB 8mb partition selected."
|
||||||
IF %BIGDB16% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 16mb partition selected."
|
IF %BIGDB16% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 16mb partition selected."
|
||||||
|
|
||||||
@REM Extract BASENAME from %FILENAME% for later use.
|
@REM Extract BASENAME from %FILENAME% for later use.
|
||||||
@@ -218,6 +228,12 @@ IF %BIGDB8% EQU 1 (
|
|||||||
SET "SPIFFS_OFFSET=0x670000"
|
SET "SPIFFS_OFFSET=0x670000"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@REM Offsets for MUIDB 8mb.
|
||||||
|
IF %MUIDB8% EQU 1 (
|
||||||
|
SET "OTA_OFFSET=0x5D0000"
|
||||||
|
SET "SPIFFS_OFFSET=0x670000"
|
||||||
|
)
|
||||||
|
|
||||||
@REM Offsets for BigDB 16mb.
|
@REM Offsets for BigDB 16mb.
|
||||||
IF %BIGDB16% EQU 1 (
|
IF %BIGDB16% EQU 1 (
|
||||||
SET "OTA_OFFSET=0x650000"
|
SET "OTA_OFFSET=0x650000"
|
||||||
|
|||||||
@@ -5,38 +5,43 @@ BPS_RESET=false
|
|||||||
TFT_BUILD=false
|
TFT_BUILD=false
|
||||||
MCU=""
|
MCU=""
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
RESET_BAUD=1200
|
||||||
|
FIRMWARE_OFFSET=0x00
|
||||||
|
|
||||||
# Variant groups
|
# Variant groups
|
||||||
BIGDB_8MB=(
|
BIGDB_8MB=(
|
||||||
"picomputer-s3"
|
"crowpanel-esp32s3"
|
||||||
"unphone"
|
"heltec_capsule_sensor_v3"
|
||||||
"seeed-sensecap-indicator"
|
"heltec-v3"
|
||||||
"crowpanel-esp32s3"
|
"heltec-vision-master-e213"
|
||||||
"heltec_capsule_sensor_v3"
|
"heltec-vision-master-e290"
|
||||||
"heltec-v3"
|
"heltec-vision-master-t190"
|
||||||
"heltec-vision-master-e213"
|
"heltec-wireless-paper"
|
||||||
"heltec-vision-master-e290"
|
"heltec-wireless-tracker"
|
||||||
"heltec-vision-master-t190"
|
"heltec-wsl-v3"
|
||||||
"heltec-wireless-paper"
|
"icarus"
|
||||||
"heltec-wireless-tracker"
|
"seeed-xiao-s3"
|
||||||
"heltec-wsl-v3"
|
"tbeam-s3-core"
|
||||||
"icarus"
|
"tracksenger"
|
||||||
"seeed-xiao-s3"
|
)
|
||||||
"tbeam-s3-core"
|
MUIDB_8MB=(
|
||||||
"tracksenger"
|
"picomputer-s3"
|
||||||
|
"unphone"
|
||||||
|
"seeed-sensecap-indicator"
|
||||||
)
|
)
|
||||||
BIGDB_16MB=(
|
BIGDB_16MB=(
|
||||||
"t-deck"
|
"t-deck"
|
||||||
"mesh-tab"
|
"mesh-tab"
|
||||||
"t-energy-s3"
|
"t-energy-s3"
|
||||||
"dreamcatcher"
|
"dreamcatcher"
|
||||||
"ESP32-S3-Pico"
|
"ESP32-S3-Pico"
|
||||||
"m5stack-cores3"
|
"m5stack-cores3"
|
||||||
"station-g2"
|
"station-g2"
|
||||||
"t-eth-elite"
|
"t-eth-elite"
|
||||||
|
"tlora-pager"
|
||||||
"t-watch-s3"
|
"t-watch-s3"
|
||||||
"elecrow-adv-35-tft"
|
"elecrow-adv"
|
||||||
"elecrow-adv-24-28-tft"
|
|
||||||
"elecrow-adv1-43-50-70-tft"
|
|
||||||
)
|
)
|
||||||
S3_VARIANTS=(
|
S3_VARIANTS=(
|
||||||
"s3"
|
"s3"
|
||||||
@@ -47,6 +52,7 @@ S3_VARIANTS=(
|
|||||||
"station-g2"
|
"station-g2"
|
||||||
"unphone"
|
"unphone"
|
||||||
"t-eth-elite"
|
"t-eth-elite"
|
||||||
|
"tlora-pager"
|
||||||
"mesh-tab"
|
"mesh-tab"
|
||||||
"dreamcatcher"
|
"dreamcatcher"
|
||||||
"ESP32-S3-Pico"
|
"ESP32-S3-Pico"
|
||||||
@@ -106,8 +112,8 @@ while [ $# -gt 0 ]; do
|
|||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--1200bps-reset)
|
--1200bps-reset)
|
||||||
BPS_RESET=true
|
BPS_RESET=true
|
||||||
;;
|
;;
|
||||||
--) # Stop parsing options
|
--) # Stop parsing options
|
||||||
shift
|
shift
|
||||||
break
|
break
|
||||||
@@ -121,7 +127,7 @@ while [ $# -gt 0 ]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [[ $BPS_RESET == true ]]; then
|
if [[ $BPS_RESET == true ]]; then
|
||||||
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
|
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -158,6 +164,13 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
for variant in "${MUIDB_8MB[@]}"; do
|
||||||
|
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
||||||
|
OFFSET=0x670000
|
||||||
|
OTA_OFFSET=0x5D0000
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# littlefs* offset for BigDB 16mb and OTA OFFSET.
|
# littlefs* offset for BigDB 16mb and OTA OFFSET.
|
||||||
for variant in "${BIGDB_16MB[@]}"; do
|
for variant in "${BIGDB_16MB[@]}"; do
|
||||||
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
||||||
@@ -201,8 +214,8 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||||
$ESPTOOL_CMD erase_flash
|
$ESPTOOL_CMD erase-flash
|
||||||
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
|
$ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
|
||||||
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
||||||
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
||||||
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ SET "SCRIPT_NAME=%~nx0"
|
|||||||
SET "DEBUG=0"
|
SET "DEBUG=0"
|
||||||
SET "PYTHON="
|
SET "PYTHON="
|
||||||
SET "ESPTOOL_BAUD=115200"
|
SET "ESPTOOL_BAUD=115200"
|
||||||
|
SET "RESET_BAUD=1200"
|
||||||
|
SET "UPDATE_OFFSET=0x10000"
|
||||||
SET "ESPTOOL_CMD="
|
SET "ESPTOOL_CMD="
|
||||||
SET "LOGCOUNTER=0"
|
SET "LOGCOUNTER=0"
|
||||||
SET "CHANGE_MODE=0"
|
SET "CHANGE_MODE=0"
|
||||||
@@ -85,14 +87,13 @@ IF "!FILENAME:update=!"=="!FILENAME!" (
|
|||||||
)
|
)
|
||||||
|
|
||||||
:skip-filename
|
:skip-filename
|
||||||
SET "ESPTOOL_BAUD=1200"
|
|
||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
||||||
IF NOT "__%PYTHON%__"=="____" (
|
IF NOT "__%PYTHON%__"=="____" (
|
||||||
SET "ESPTOOL_CMD=!PYTHON! -m esptool"
|
SET "ESPTOOL_CMD=""!PYTHON!"" -m esptool"
|
||||||
CALL :LOG_MESSAGE DEBUG "Python interpreter supplied."
|
CALL :LOG_MESSAGE DEBUG "Python interpreter supplied."
|
||||||
) ELSE (
|
) ELSE (
|
||||||
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool...
|
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool..."
|
||||||
WHERE esptool >nul 2>&1
|
WHERE esptool >nul 2>&1
|
||||||
IF %ERRORLEVEL% EQU 0 (
|
IF %ERRORLEVEL% EQU 0 (
|
||||||
@REM WHERE exits with code 0 if esptool is found.
|
@REM WHERE exits with code 0 if esptool is found.
|
||||||
@@ -105,11 +106,11 @@ IF NOT "__%PYTHON%__"=="____" (
|
|||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
|
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
|
||||||
!ESPTOOL_CMD! >nul 2>&1
|
!ESPTOOL_CMD! >nul 2>&1
|
||||||
IF %ERRORLEVEL% GEQ 2 (
|
CALL :LOG_MESSAGE DEBUG "esptool exit code: %ERRORLEVEL%"
|
||||||
@REM esptool exits with code 1 if help is displayed.
|
IF %ERRORLEVEL% EQU 9009 (
|
||||||
|
@REM 9009 = command not found on Windows
|
||||||
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
|
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
|
||||||
EXIT /B 1
|
EXIT /B 1
|
||||||
GOTO eof
|
|
||||||
)
|
)
|
||||||
IF %DEBUG% EQU 1 (
|
IF %DEBUG% EQU 1 (
|
||||||
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
|
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
|
||||||
@@ -127,13 +128,13 @@ CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
|
|||||||
|
|
||||||
IF %CHANGE_MODE% EQU 1 (
|
IF %CHANGE_MODE% EQU 1 (
|
||||||
@REM Attempt to change mode via 1200bps Reset.
|
@REM Attempt to change mode via 1200bps Reset.
|
||||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
|
CALL :RUN_ESPTOOL !RESET_BAUD! --after no_reset read_flash_status
|
||||||
GOTO eof
|
GOTO eof
|
||||||
)
|
)
|
||||||
|
|
||||||
@REM Flashing operations.
|
@REM Flashing operations.
|
||||||
CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET 0x10000..."
|
CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET !UPDATE_OFFSET!..."
|
||||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x10000 "!FILENAME!" || GOTO eof
|
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !UPDATE_OFFSET! "!FILENAME!" || GOTO eof
|
||||||
|
|
||||||
CALL :LOG_MESSAGE INFO "Script complete!."
|
CALL :LOG_MESSAGE INFO "Script complete!."
|
||||||
|
|
||||||
@@ -145,9 +146,9 @@ EXIT /B %ERRORLEVEL%
|
|||||||
:RUN_ESPTOOL
|
:RUN_ESPTOOL
|
||||||
@REM Subroutine used to run ESPTOOL_CMD with arguments.
|
@REM Subroutine used to run ESPTOOL_CMD with arguments.
|
||||||
@REM Also handles %ERRORLEVEL%.
|
@REM Also handles %ERRORLEVEL%.
|
||||||
@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write_flash] [OFFSET] [Filename]
|
@REM CALL :RUN_ESPTOOL [Baud] [erase-flash|write-flash] [OFFSET] [Filename]
|
||||||
@REM.
|
@REM.
|
||||||
@REM Example:: CALL :RUN_ESPTOOL 115200 write_flash 0x10000 "firmwarefile.bin"
|
@REM Example:: CALL :RUN_ESPTOOL 115200 write-flash 0x10000 "firmwarefile.bin"
|
||||||
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
|
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
|
||||||
CALL :RESET_ERROR
|
CALL :RESET_ERROR
|
||||||
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
|
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
|
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
|
||||||
CHANGE_MODE=false
|
CHANGE_MODE=false
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
FLASH_BAUD=115200
|
||||||
|
RESET_BAUD=1200
|
||||||
|
UPDATE_OFFSET=0x10000
|
||||||
|
|
||||||
# Determine the correct esptool command to use
|
# Determine the correct esptool command to use
|
||||||
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
|
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
|
||||||
ESPTOOL_CMD="$PYTHON -m esptool"
|
ESPTOOL_CMD="$PYTHON -m esptool"
|
||||||
@@ -64,7 +69,7 @@ done
|
|||||||
shift "$((OPTIND-1))"
|
shift "$((OPTIND-1))"
|
||||||
|
|
||||||
if [ "$CHANGE_MODE" = true ]; then
|
if [ "$CHANGE_MODE" = true ]; then
|
||||||
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
|
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -75,7 +80,7 @@ fi
|
|||||||
|
|
||||||
if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
|
if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
|
||||||
echo "Trying to flash update ${FILENAME}"
|
echo "Trying to flash update ${FILENAME}"
|
||||||
$ESPTOOL_CMD --baud 115200 write_flash 0x10000 "${FILENAME}"
|
$ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
|
||||||
else
|
else
|
||||||
show_help
|
show_help
|
||||||
echo "Invalid file: ${FILENAME}"
|
echo "Invalid file: ${FILENAME}"
|
||||||
|
|||||||
@@ -87,6 +87,15 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="2.7.9" date="2025-09-03">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.8" date="2025-08-30">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.8</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.7" date="2025-08-28">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.7</url>
|
||||||
|
</release>
|
||||||
<release version="2.7.6" date="2025-08-12">
|
<release version="2.7.6" date="2025-08-12">
|
||||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6</url>
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ from os.path import join
|
|||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from readprops import readProps
|
from readprops import readProps
|
||||||
|
|
||||||
@@ -125,11 +127,16 @@ for pref in userPrefs:
|
|||||||
pref_flags.append("-D" + pref + "=" + env.StringifyMacro(userPrefs[pref]) + "")
|
pref_flags.append("-D" + pref + "=" + env.StringifyMacro(userPrefs[pref]) + "")
|
||||||
|
|
||||||
# General options that are passed to the C and C++ compilers
|
# General options that are passed to the C and C++ compilers
|
||||||
|
# Calculate unix epoch for current day (midnight)
|
||||||
|
current_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
build_epoch = int(current_date.timestamp())
|
||||||
|
|
||||||
flags = [
|
flags = [
|
||||||
"-DAPP_VERSION=" + verObj["long"],
|
"-DAPP_VERSION=" + verObj["long"],
|
||||||
"-DAPP_VERSION_SHORT=" + verObj["short"],
|
"-DAPP_VERSION_SHORT=" + verObj["short"],
|
||||||
"-DAPP_ENV=" + env.get("PIOENV"),
|
"-DAPP_ENV=" + env.get("PIOENV"),
|
||||||
"-DAPP_REPO=" + repo_owner,
|
"-DAPP_REPO=" + repo_owner,
|
||||||
|
"-DBUILD_EPOCH=" + str(build_epoch),
|
||||||
] + pref_flags
|
] + pref_flags
|
||||||
|
|
||||||
print ("Using flags:")
|
print ("Using flags:")
|
||||||
|
|||||||
54
boards/heltec_mesh_solar.json
Normal file
54
boards/heltec_mesh_solar.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "nrf52840_s140_v6.ld"
|
||||||
|
},
|
||||||
|
"core": "nRF5",
|
||||||
|
"cpu": "cortex-m4",
|
||||||
|
"extra_flags": "-DNRF52840_XXAA",
|
||||||
|
"f_cpu": "64000000L",
|
||||||
|
"hwids": [
|
||||||
|
["0x239A", "0x4405"],
|
||||||
|
["0x239A", "0x0029"],
|
||||||
|
["0x239A", "0x002A"],
|
||||||
|
["0x239A", "0x0071"]
|
||||||
|
],
|
||||||
|
"usb_product": "HT-n5262",
|
||||||
|
"mcu": "nrf52840",
|
||||||
|
"variant": "heltec_mesh_solar",
|
||||||
|
"variants_dir": "variants",
|
||||||
|
"bsp": {
|
||||||
|
"name": "adafruit"
|
||||||
|
},
|
||||||
|
"softdevice": {
|
||||||
|
"sd_flags": "-DS140",
|
||||||
|
"sd_name": "s140",
|
||||||
|
"sd_version": "6.1.1",
|
||||||
|
"sd_fwid": "0x00B6"
|
||||||
|
},
|
||||||
|
"bootloader": {
|
||||||
|
"settings_addr": "0xFF000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"connectivity": ["bluetooth"],
|
||||||
|
"debug": {
|
||||||
|
"jlink_device": "nRF52840_xxAA",
|
||||||
|
"onboard_tools": ["jlink"],
|
||||||
|
"svd_path": "nrf52840.svd",
|
||||||
|
"openocd_target": "nrf52840-mdk-rs"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino"],
|
||||||
|
"name": "Heltec nrf (Adafruit BSP)",
|
||||||
|
"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://heltec.org/project/meshsolar/",
|
||||||
|
"vendor": "Heltec"
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"arduino": {
|
"arduino": {
|
||||||
"ldscript": "esp32s3_out.ld",
|
"ldscript": "esp32s3_out.ld",
|
||||||
"partitions": "default_8MB.csv",
|
"partitions": "partition-table-8MB.csv",
|
||||||
"memory_type": "qio_opi"
|
"memory_type": "qio_opi"
|
||||||
},
|
},
|
||||||
"core": "esp32",
|
"core": "esp32",
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"f_cpu": "240000000L",
|
"f_cpu": "240000000L",
|
||||||
"f_flash": "80000000L",
|
"f_flash": "80000000L",
|
||||||
"f_boot": "120000000L",
|
"f_boot": "120000000L",
|
||||||
|
"boot_freq": "120000000L",
|
||||||
"boot": "qio",
|
"boot": "qio",
|
||||||
"flash_mode": "qio",
|
"flash_mode": "qio",
|
||||||
"psram_type": "opi",
|
"psram_type": "opi",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"arduino": {
|
"arduino": {
|
||||||
"ldscript": "esp32s3_out.ld",
|
"ldscript": "esp32s3_out.ld",
|
||||||
"memory_type": "qio_opi",
|
"memory_type": "qio_opi",
|
||||||
"partitions": "default_8MB.csv"
|
"partitions": "partition-table-8MB.csv"
|
||||||
},
|
},
|
||||||
"core": "esp32",
|
"core": "esp32",
|
||||||
"extra_flags": [
|
"extra_flags": [
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
},
|
},
|
||||||
"core": "stm32",
|
"core": "stm32",
|
||||||
"cpu": "cortex-m4",
|
"cpu": "cortex-m4",
|
||||||
"extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_GENERIC_WLE5CCUX",
|
"extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_RAK3172_MODULE",
|
||||||
"f_cpu": "48000000L",
|
"f_cpu": "48000000L",
|
||||||
"mcu": "stm32wle5ccu",
|
"mcu": "stm32wle5ccu",
|
||||||
"variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U",
|
"variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U",
|
||||||
|
|||||||
11
debian/changelog
vendored
11
debian/changelog
vendored
@@ -1,4 +1,4 @@
|
|||||||
meshtasticd (2.7.6.0) UNRELEASED; urgency=medium
|
meshtasticd (2.7.9.0) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
[ Austin Lane ]
|
[ Austin Lane ]
|
||||||
* Initial packaging
|
* Initial packaging
|
||||||
@@ -39,5 +39,12 @@ meshtasticd (2.7.6.0) UNRELEASED; urgency=medium
|
|||||||
|
|
||||||
[ ]
|
[ ]
|
||||||
* GitHub Actions Automatic version bump
|
* GitHub Actions Automatic version bump
|
||||||
|
* GitHub Actions Automatic version bump
|
||||||
|
|
||||||
-- <github-actions[bot]@users.noreply.github.com> Tue, 12 Aug 2025 23:48:48 +0000
|
[ ]
|
||||||
|
* GitHub Actions Automatic version bump
|
||||||
|
|
||||||
|
[ ]
|
||||||
|
* GitHub Actions Automatic version bump
|
||||||
|
|
||||||
|
-- <github-actions[bot]@users.noreply.github.com> Wed, 03 Sep 2025 23:39:17 +0000
|
||||||
|
|||||||
7
partition-table-8MB.csv
Normal file
7
partition-table-8MB.csv
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# This is a layout for 8MB of flash for MUI devices
|
||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
|
otadata, data, ota, 0xe000, 0x2000,
|
||||||
|
app0, app, ota_0, 0x10000, 0x5C0000,
|
||||||
|
flashApp, app, ota_1, 0x5D0000,0x0A0000,
|
||||||
|
spiffs, data, spiffs, 0x670000,0x180000
|
||||||
|
@@ -53,14 +53,14 @@ build_flags = -Wno-missing-field-initializers
|
|||||||
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
|
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
|
||||||
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
|
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
|
||||||
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
|
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
|
||||||
#-DBUILD_EPOCH=$UNIX_TIME
|
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
|
||||||
#-D OLED_PL=1
|
#-D OLED_PL=1
|
||||||
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_filters = direct
|
monitor_filters = direct
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
||||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/9573abb64dc9c94f3051348f2bf4fc5cedf03c22.zip
|
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
|
||||||
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
|
||||||
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
|
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
|
||||||
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
||||||
@@ -87,8 +87,9 @@ check_flags =
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
|
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=mverch67/library/NonBlockingRTTTL
|
||||||
end2endzone/NonBlockingRTTTL@1.3.0
|
https://github.com/mverch67/NonBlockingRTTTL/archive/ad1c2fb12bc81db546c6a94e963acb3382d3689e.zip ; TODO
|
||||||
|
|
||||||
build_flags = ${env.build_flags} -Os
|
build_flags = ${env.build_flags} -Os
|
||||||
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
|
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
|
||||||
|
|
||||||
@@ -99,8 +100,6 @@ lib_deps =
|
|||||||
thingsboard/TBPubSubClient@2.12.1
|
thingsboard/TBPubSubClient@2.12.1
|
||||||
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
|
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
|
||||||
arduino-libraries/NTPClient@3.2.1
|
arduino-libraries/NTPClient@3.2.1
|
||||||
# renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog
|
|
||||||
arcao/Syslog@2.0.0
|
|
||||||
|
|
||||||
; Minimal networking libs for nrf52 (excludes Syslog to save flash)
|
; Minimal networking libs for nrf52 (excludes Syslog to save flash)
|
||||||
[nrf52_networking_base]
|
[nrf52_networking_base]
|
||||||
@@ -118,7 +117,7 @@ lib_deps =
|
|||||||
[device-ui_base]
|
[device-ui_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||||
https://github.com/meshtastic/device-ui/archive/3dc7cf3e233aaa8cc23492cca50541fc099ebfa1.zip
|
https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip
|
||||||
|
|
||||||
; Common libs for environmental measurements in telemetry module
|
; Common libs for environmental measurements in telemetry module
|
||||||
[environmental_base]
|
[environmental_base]
|
||||||
@@ -157,8 +156,8 @@ lib_deps =
|
|||||||
emotibit/EmotiBit MLX90632@1.0.8
|
emotibit/EmotiBit MLX90632@1.0.8
|
||||||
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
|
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
|
||||||
adafruit/Adafruit MLX90614 Library@2.1.5
|
adafruit/Adafruit MLX90614 Library@2.1.5
|
||||||
# renovate: datasource=github-tags depName=INA3221 packageName=KodinLanewave/INA3221
|
# renovate: datasource=github-tags depName=INA3221 packageName=sgtwilko/INA3221
|
||||||
https://github.com/KodinLanewave/INA3221/archive/1.0.1.zip
|
https://github.com/sgtwilko/INA3221#bb03d7e9bfcc74fc798838a54f4f99738f29fc6a
|
||||||
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
|
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
|
||||||
mprograms/QMC5883LCompass@1.2.3
|
mprograms/QMC5883LCompass@1.2.3
|
||||||
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
|
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
|
||||||
@@ -177,6 +176,8 @@ lib_deps =
|
|||||||
adafruit/Adafruit PCT2075@1.0.5
|
adafruit/Adafruit PCT2075@1.0.5
|
||||||
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
|
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
|
||||||
dfrobot/DFRobot_BMM150@1.0.0
|
dfrobot/DFRobot_BMM150@1.0.0
|
||||||
|
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
|
||||||
|
adafruit/Adafruit TSL2561@1.1.2
|
||||||
|
|
||||||
; (not included in native / portduino)
|
; (not included in native / portduino)
|
||||||
[environmental_extra]
|
[environmental_extra]
|
||||||
|
|||||||
Submodule protobufs updated: 8985852d75...945b796a98
@@ -183,9 +183,9 @@ class AmbientLightingThread : public concurrency::OSThread
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
pixels.show();
|
pixels.show();
|
||||||
LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
|
// LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
|
||||||
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
|
// moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
|
||||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
// moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||||
#endif
|
#endif
|
||||||
#ifdef RGBLED_CA
|
#ifdef RGBLED_CA
|
||||||
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
|
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
#if defined(DEBUG_HEAP)
|
||||||
|
class MemGet;
|
||||||
|
extern MemGet memGet;
|
||||||
|
#endif
|
||||||
|
|
||||||
// DEBUG LED
|
// DEBUG LED
|
||||||
#ifndef LED_STATE_ON
|
#ifndef LED_STATE_ON
|
||||||
#define LED_STATE_ON 1
|
#define LED_STATE_ON 1
|
||||||
@@ -23,6 +29,7 @@
|
|||||||
#define MESHTASTIC_LOG_LEVEL_ERROR "ERROR"
|
#define MESHTASTIC_LOG_LEVEL_ERROR "ERROR"
|
||||||
#define MESHTASTIC_LOG_LEVEL_CRIT "CRIT "
|
#define MESHTASTIC_LOG_LEVEL_CRIT "CRIT "
|
||||||
#define MESHTASTIC_LOG_LEVEL_TRACE "TRACE"
|
#define MESHTASTIC_LOG_LEVEL_TRACE "TRACE"
|
||||||
|
#define MESHTASTIC_LOG_LEVEL_HEAP "HEAP"
|
||||||
|
|
||||||
#include "SerialConsole.h"
|
#include "SerialConsole.h"
|
||||||
|
|
||||||
@@ -62,6 +69,25 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(DEBUG_HEAP)
|
||||||
|
#define LOG_HEAP(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__)
|
||||||
|
|
||||||
|
// Macro-based heap debugging
|
||||||
|
#define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap();
|
||||||
|
#define DEBUG_HEAP_AFTER(context, ptr) \
|
||||||
|
do { \
|
||||||
|
auto heapAfter = memGet.getFreeHeap(); \
|
||||||
|
if (heapBefore != heapAfter) { \
|
||||||
|
LOG_HEAP("Alloc in %s pointer 0x%x, size: %u, free: %u", context, ptr, heapBefore - heapAfter, heapAfter); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define LOG_HEAP(...)
|
||||||
|
#define DEBUG_HEAP_BEFORE
|
||||||
|
#define DEBUG_HEAP_AFTER(context, ptr)
|
||||||
|
#endif
|
||||||
|
|
||||||
/// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic
|
/// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic
|
||||||
extern "C" void logLegacy(const char *level, const char *fmt, ...);
|
extern "C" void logLegacy(const char *level, const char *fmt, ...);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
#include "DisplayFormatters.h"
|
#include "DisplayFormatters.h"
|
||||||
|
|
||||||
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName)
|
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
|
||||||
|
bool usePreset)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// If use_preset is false, always return "Custom"
|
||||||
|
if (!usePreset) {
|
||||||
|
return "Custom";
|
||||||
|
}
|
||||||
|
|
||||||
switch (preset) {
|
switch (preset) {
|
||||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
|
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
|
||||||
return useShortName ? "ShortT" : "ShortTurbo";
|
return useShortName ? "ShortT" : "ShortTurbo";
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
class DisplayFormatters
|
class DisplayFormatters
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName);
|
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
|
||||||
|
bool usePreset);
|
||||||
};
|
};
|
||||||
|
|||||||
153
src/Power.cpp
153
src/Power.cpp
@@ -128,6 +128,7 @@ RAK9154Sensor rak9154Sensor;
|
|||||||
#ifdef HAS_PPM
|
#ifdef HAS_PPM
|
||||||
// note: XPOWERS_CHIP_XXX must be defined in variant.h
|
// note: XPOWERS_CHIP_XXX must be defined in variant.h
|
||||||
#include <XPowersLib.h>
|
#include <XPowersLib.h>
|
||||||
|
XPowersPPM *PPM = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_BQ27220
|
#ifdef HAS_BQ27220
|
||||||
@@ -681,6 +682,8 @@ bool Power::setup()
|
|||||||
found = true;
|
found = true;
|
||||||
} else if (lipoChargerInit()) {
|
} else if (lipoChargerInit()) {
|
||||||
found = true;
|
found = true;
|
||||||
|
} else if (meshSolarInit()) {
|
||||||
|
found = true;
|
||||||
} else if (analogInit()) {
|
} else if (analogInit()) {
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
@@ -743,7 +746,11 @@ void Power::shutdown()
|
|||||||
|
|
||||||
#if HAS_SCREEN
|
#if HAS_SCREEN
|
||||||
if (screen) {
|
if (screen) {
|
||||||
|
#ifdef T_DECK_PRO
|
||||||
|
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
|
||||||
|
#else
|
||||||
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if !defined(ARCH_STM32WL)
|
#if !defined(ARCH_STM32WL)
|
||||||
@@ -761,7 +768,7 @@ void Power::shutdown()
|
|||||||
#ifdef PIN_LED3
|
#ifdef PIN_LED3
|
||||||
ledOff(PIN_LED3);
|
ledOff(PIN_LED3);
|
||||||
#endif
|
#endif
|
||||||
doDeepSleep(DELAY_FOREVER, false, true);
|
doDeepSleep(DELAY_FOREVER, true, true);
|
||||||
#elif defined(ARCH_PORTDUINO)
|
#elif defined(ARCH_PORTDUINO)
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
#else
|
#else
|
||||||
@@ -826,18 +833,27 @@ void Power::readPowerStatus()
|
|||||||
newStatus.notifyObservers(&powerStatus2);
|
newStatus.notifyObservers(&powerStatus2);
|
||||||
#ifdef DEBUG_HEAP
|
#ifdef DEBUG_HEAP
|
||||||
if (lastheap != memGet.getFreeHeap()) {
|
if (lastheap != memGet.getFreeHeap()) {
|
||||||
std::string threadlist = "Threads running:";
|
// Use stack-allocated buffer to avoid heap allocations in monitoring code
|
||||||
|
char threadlist[256] = "Threads running:";
|
||||||
|
int threadlistLen = strlen(threadlist);
|
||||||
int running = 0;
|
int running = 0;
|
||||||
for (int i = 0; i < MAX_THREADS; i++) {
|
for (int i = 0; i < MAX_THREADS; i++) {
|
||||||
auto thread = concurrency::mainController.get(i);
|
auto thread = concurrency::mainController.get(i);
|
||||||
if ((thread != nullptr) && (thread->enabled)) {
|
if ((thread != nullptr) && (thread->enabled)) {
|
||||||
threadlist += vformat(" %s", thread->ThreadName.c_str());
|
// Use snprintf to safely append to stack buffer without heap allocation
|
||||||
|
int remaining = sizeof(threadlist) - threadlistLen - 1;
|
||||||
|
if (remaining > 0) {
|
||||||
|
int written = snprintf(threadlist + threadlistLen, remaining, " %s", thread->ThreadName.c_str());
|
||||||
|
if (written > 0 && written < remaining) {
|
||||||
|
threadlistLen += written;
|
||||||
|
}
|
||||||
|
}
|
||||||
running++;
|
running++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_DEBUG(threadlist.c_str());
|
LOG_HEAP(threadlist);
|
||||||
LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
|
LOG_HEAP("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
|
||||||
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
|
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
|
||||||
lastheap = memGet.getFreeHeap();
|
lastheap = memGet.getFreeHeap();
|
||||||
}
|
}
|
||||||
#ifdef DEBUG_HEAP_MQTT
|
#ifdef DEBUG_HEAP_MQTT
|
||||||
@@ -849,15 +865,19 @@ void Power::readPowerStatus()
|
|||||||
sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]);
|
sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]);
|
||||||
|
|
||||||
auto newHeap = memGet.getFreeHeap();
|
auto newHeap = memGet.getFreeHeap();
|
||||||
std::string heapTopic =
|
// Use stack-allocated buffers to avoid heap allocations in monitoring code
|
||||||
(*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/heap/") + std::string(mac);
|
char heapTopic[128];
|
||||||
std::string heapString = std::to_string(newHeap);
|
snprintf(heapTopic, sizeof(heapTopic), "%s/2/heap/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac);
|
||||||
mqtt->pubSub.publish(heapTopic.c_str(), heapString.c_str(), false);
|
char heapString[16];
|
||||||
|
snprintf(heapString, sizeof(heapString), "%u", newHeap);
|
||||||
|
mqtt->pubSub.publish(heapTopic, heapString, false);
|
||||||
|
|
||||||
auto wifiRSSI = WiFi.RSSI();
|
auto wifiRSSI = WiFi.RSSI();
|
||||||
std::string wifiTopic =
|
char wifiTopic[128];
|
||||||
(*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/wifi/") + std::string(mac);
|
snprintf(wifiTopic, sizeof(wifiTopic), "%s/2/wifi/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac);
|
||||||
std::string wifiString = std::to_string(wifiRSSI);
|
char wifiString[16];
|
||||||
mqtt->pubSub.publish(wifiTopic.c_str(), wifiString.c_str(), false);
|
snprintf(wifiString, sizeof(wifiString), "%d", wifiRSSI);
|
||||||
|
mqtt->pubSub.publish(wifiTopic, wifiString, false);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1318,7 +1338,6 @@ bool Power::lipoInit()
|
|||||||
class LipoCharger : public HasBatteryLevel
|
class LipoCharger : public HasBatteryLevel
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
XPowersPPM *ppm = nullptr;
|
|
||||||
BQ27220 *bq = nullptr;
|
BQ27220 *bq = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -1327,41 +1346,41 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
*/
|
*/
|
||||||
bool runOnce()
|
bool runOnce()
|
||||||
{
|
{
|
||||||
if (ppm == nullptr) {
|
if (PPM == nullptr) {
|
||||||
ppm = new XPowersPPM;
|
PPM = new XPowersPPM;
|
||||||
bool result = ppm->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
|
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
|
||||||
if (result) {
|
if (result) {
|
||||||
LOG_INFO("PPM BQ25896 init succeeded");
|
LOG_INFO("PPM BQ25896 init succeeded");
|
||||||
// Set the minimum operating voltage. Below this voltage, the PPM will protect
|
// Set the minimum operating voltage. Below this voltage, the PPM will protect
|
||||||
// ppm->setSysPowerDownVoltage(3100);
|
// PPM->setSysPowerDownVoltage(3100);
|
||||||
|
|
||||||
// Set input current limit, default is 500mA
|
// Set input current limit, default is 500mA
|
||||||
// ppm->setInputCurrentLimit(800);
|
// PPM->setInputCurrentLimit(800);
|
||||||
|
|
||||||
// Disable current limit pin
|
// Disable current limit pin
|
||||||
// ppm->disableCurrentLimitPin();
|
// PPM->disableCurrentLimitPin();
|
||||||
|
|
||||||
// Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV
|
// Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV
|
||||||
ppm->setChargeTargetVoltage(4288);
|
PPM->setChargeTargetVoltage(4288);
|
||||||
|
|
||||||
// Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA
|
// Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA
|
||||||
// ppm->setPrechargeCurr(64);
|
// PPM->setPrechargeCurr(64);
|
||||||
|
|
||||||
// The premise is that limit pin is disabled, or it will
|
// The premise is that limit pin is disabled, or it will
|
||||||
// only follow the maximum charging current set by limit pin.
|
// only follow the maximum charging current set by limit pin.
|
||||||
// Set the charging current , Range:0~5056mA ,step:64mA
|
// Set the charging current , Range:0~5056mA ,step:64mA
|
||||||
ppm->setChargerConstantCurr(1024);
|
PPM->setChargerConstantCurr(1024);
|
||||||
|
|
||||||
// To obtain voltage data, the ADC must be enabled first
|
// To obtain voltage data, the ADC must be enabled first
|
||||||
ppm->enableMeasure();
|
PPM->enableMeasure();
|
||||||
|
|
||||||
// Turn on charging function
|
// Turn on charging function
|
||||||
// If there is no battery connected, do not turn on the charging function
|
// If there is no battery connected, do not turn on the charging function
|
||||||
ppm->enableCharge();
|
PPM->enableCharge();
|
||||||
} else {
|
} else {
|
||||||
LOG_WARN("PPM BQ25896 init failed");
|
LOG_WARN("PPM BQ25896 init failed");
|
||||||
delete ppm;
|
delete PPM;
|
||||||
ppm = nullptr;
|
PPM = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1402,23 +1421,23 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
/**
|
/**
|
||||||
* return true if there is a battery installed in this unit
|
* return true if there is a battery installed in this unit
|
||||||
*/
|
*/
|
||||||
virtual bool isBatteryConnect() override { return ppm->getBattVoltage() > 0; }
|
virtual bool isBatteryConnect() override { return PPM->getBattVoltage() > 0; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return true if there is an external power source detected
|
* return true if there is an external power source detected
|
||||||
*/
|
*/
|
||||||
virtual bool isVbusIn() override { return ppm->getVbusVoltage() > 0; }
|
virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return true if the battery is currently charging
|
* return true if the battery is currently charging
|
||||||
*/
|
*/
|
||||||
virtual bool isCharging() override
|
virtual bool isCharging() override
|
||||||
{
|
{
|
||||||
bool isCharging = ppm->isCharging();
|
bool isCharging = PPM->isCharging();
|
||||||
if (isCharging) {
|
if (isCharging) {
|
||||||
LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull());
|
LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull());
|
||||||
} else {
|
} else {
|
||||||
if (!ppm->isVbusIn()) {
|
if (!PPM->isVbusIn()) {
|
||||||
LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity());
|
LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1450,3 +1469,73 @@ bool Power::lipoChargerInit()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HELTEC_MESH_SOLAR
|
||||||
|
#include "meshSolarApp.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* meshSolar class for an SMBUS battery sensor.
|
||||||
|
*/
|
||||||
|
class meshSolarBatteryLevel : public HasBatteryLevel
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Init the I2C meshSolar battery level sensor
|
||||||
|
*/
|
||||||
|
bool runOnce()
|
||||||
|
{
|
||||||
|
meshSolarStart();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||||
|
*/
|
||||||
|
virtual int getBatteryPercent() override { return meshSolarGetBatteryPercent(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||||||
|
*/
|
||||||
|
virtual uint16_t getBattVoltage() override { return meshSolarGetBattVoltage(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if there is a battery installed in this unit
|
||||||
|
*/
|
||||||
|
virtual bool isBatteryConnect() override { return meshSolarIsBatteryConnect(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if there is an external power source detected
|
||||||
|
*/
|
||||||
|
virtual bool isVbusIn() override { return meshSolarIsVbusIn(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if the battery is currently charging
|
||||||
|
*/
|
||||||
|
virtual bool isCharging() override { return meshSolarIsCharging(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
meshSolarBatteryLevel meshSolarLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init the meshSolar battery level sensor
|
||||||
|
*/
|
||||||
|
bool Power::meshSolarInit()
|
||||||
|
{
|
||||||
|
bool result = meshSolarLevel.runOnce();
|
||||||
|
LOG_DEBUG("Power::meshSolarInit mesh solar sensor is %s", result ? "ready" : "not ready yet");
|
||||||
|
if (!result)
|
||||||
|
return false;
|
||||||
|
batteryLevel = &meshSolarLevel;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
/**
|
||||||
|
* The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel
|
||||||
|
*/
|
||||||
|
bool Power::meshSolarInit()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -64,6 +64,14 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
|
|||||||
|
|
||||||
int32_t SerialConsole::runOnce()
|
int32_t SerialConsole::runOnce()
|
||||||
{
|
{
|
||||||
|
#ifdef HELTEC_MESH_SOLAR
|
||||||
|
//After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
|
||||||
|
if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port
|
||||||
|
&& moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)
|
||||||
|
{
|
||||||
|
return 250;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return runOncePart();
|
return runOncePart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,9 +86,9 @@ void OSThread::run()
|
|||||||
#ifdef DEBUG_HEAP
|
#ifdef DEBUG_HEAP
|
||||||
auto newHeap = memGet.getFreeHeap();
|
auto newHeap = memGet.getFreeHeap();
|
||||||
if (newHeap < heap)
|
if (newHeap < heap)
|
||||||
LOG_DEBUG("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap);
|
LOG_HEAP("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap);
|
||||||
if (heap < newHeap)
|
if (heap < newHeap)
|
||||||
LOG_DEBUG("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
|
LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
runned();
|
runned();
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#ifdef RV3028_RTC
|
#if __has_include("Melopero_RV3028.h")
|
||||||
#include "Melopero_RV3028.h"
|
#include "Melopero_RV3028.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef PCF8563_RTC
|
#if __has_include("pcf8563.h")
|
||||||
#include "pcf8563.h"
|
#include "pcf8563.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// OLED & Input
|
// OLED & Input
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
#if defined(SEEED_WIO_TRACKER_L1)
|
#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||||
#define SSD1306_ADDRESS 0x3D
|
#define SSD1306_ADDRESS 0x3D
|
||||||
#define USE_SH1106
|
#define USE_SH1106
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ class ScanI2C
|
|||||||
BQ27220,
|
BQ27220,
|
||||||
LTR553ALS,
|
LTR553ALS,
|
||||||
BHI260AP,
|
BHI260AP,
|
||||||
BMM150
|
BMM150,
|
||||||
|
TSL2561,
|
||||||
|
DRV2605
|
||||||
} DeviceType;
|
} DeviceType;
|
||||||
|
|
||||||
// typedef uint8_t DeviceAddress;
|
// typedef uint8_t DeviceAddress;
|
||||||
|
|||||||
@@ -294,6 +294,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
type = AHT10;
|
type = AHT10;
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
case INA_ADDR:
|
case INA_ADDR:
|
||||||
case INA_ADDR_ALTERNATE:
|
case INA_ADDR_ALTERNATE:
|
||||||
case INA_ADDR_WAVESHARE_UPS:
|
case INA_ADDR_WAVESHARE_UPS:
|
||||||
@@ -340,6 +341,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
// else: probably a RAK12500/UBLOX GPS on I2C
|
// else: probably a RAK12500/UBLOX GPS on I2C
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
case MCP9808_ADDR:
|
case MCP9808_ADDR:
|
||||||
// We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some
|
// We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some
|
||||||
// weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips.
|
// weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips.
|
||||||
@@ -461,7 +463,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591", (uint8_t)addr.address);
|
case TSL25911_ADDR:
|
||||||
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
|
||||||
|
if (registerValue == 0x50) {
|
||||||
|
type = TSL2591;
|
||||||
|
logFoundDevice("TSL25911", (uint8_t)addr.address);
|
||||||
|
} else {
|
||||||
|
type = TSL2561;
|
||||||
|
logFoundDevice("TSL2561", (uint8_t)addr.address);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
|
||||||
@@ -483,8 +495,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
type = MLX90614;
|
type = MLX90614;
|
||||||
logFoundDevice("MLX90614", (uint8_t)addr.address);
|
logFoundDevice("MLX90614", (uint8_t)addr.address);
|
||||||
} else {
|
} else {
|
||||||
type = MPR121KB;
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // DRV2605_REG_STATUS
|
||||||
logFoundDevice("MPR121KB", (uint8_t)addr.address);
|
if (registerValue == 0xe0) {
|
||||||
|
type = DRV2605;
|
||||||
|
logFoundDevice("DRV2605", (uint8_t)addr.address);
|
||||||
|
} else {
|
||||||
|
type = MPR121KB;
|
||||||
|
logFoundDevice("MPR121KB", (uint8_t)addr.address);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
131
src/gps/GPS.cpp
131
src/gps/GPS.cpp
@@ -1,5 +1,4 @@
|
|||||||
#include <cstring> // Include for strstr
|
#include <cstring> // Include for strstr
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
@@ -843,9 +842,6 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
|
|||||||
setPowerPMU(true); // Power (PMU): on
|
setPowerPMU(true); // Power (PMU): on
|
||||||
writePinStandby(false); // Standby (pin): awake (not standby)
|
writePinStandby(false); // Standby (pin): awake (not standby)
|
||||||
setPowerUBLOX(true); // Standby (UBLOX): awake
|
setPowerUBLOX(true); // Standby (UBLOX): awake
|
||||||
#ifdef GNSS_AIROHA
|
|
||||||
lastFixStartMsec = 0;
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GPS_SOFTSLEEP:
|
case GPS_SOFTSLEEP:
|
||||||
@@ -863,9 +859,7 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
|
|||||||
writePinStandby(true); // Standby (pin): asleep (not awake)
|
writePinStandby(true); // Standby (pin): asleep (not awake)
|
||||||
setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed
|
setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed
|
||||||
#ifdef GNSS_AIROHA
|
#ifdef GNSS_AIROHA
|
||||||
if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) {
|
digitalWrite(PIN_GPS_EN, LOW);
|
||||||
digitalWrite(PIN_GPS_EN, LOW);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -877,9 +871,7 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
|
|||||||
writePinStandby(true); // Standby (pin): asleep
|
writePinStandby(true); // Standby (pin): asleep
|
||||||
setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely
|
setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely
|
||||||
#ifdef GNSS_AIROHA
|
#ifdef GNSS_AIROHA
|
||||||
if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) {
|
digitalWrite(PIN_GPS_EN, LOW);
|
||||||
digitalWrite(PIN_GPS_EN, LOW);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1062,6 +1054,8 @@ void GPS::down()
|
|||||||
}
|
}
|
||||||
// If update interval long enough (or softsleep unsupported): hardsleep instead
|
// If update interval long enough (or softsleep unsupported): hardsleep instead
|
||||||
setPowerState(GPS_HARDSLEEP, sleepTime);
|
setPowerState(GPS_HARDSLEEP, sleepTime);
|
||||||
|
// Reset the fix quality to 0, since we're off.
|
||||||
|
fixQual = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,11 +1115,19 @@ int32_t GPS::runOnce()
|
|||||||
shouldPublish = true;
|
shouldPublish = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t prev_fixQual = fixQual;
|
||||||
bool gotLoc = lookForLocation();
|
bool gotLoc = lookForLocation();
|
||||||
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
|
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
|
||||||
LOG_DEBUG("hasValidLocation RISING EDGE");
|
LOG_DEBUG("hasValidLocation RISING EDGE");
|
||||||
hasValidLocation = true;
|
hasValidLocation = true;
|
||||||
shouldPublish = 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
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tooLong = scheduling.searchedTooLong();
|
bool tooLong = scheduling.searchedTooLong();
|
||||||
@@ -1134,8 +1136,7 @@ int32_t GPS::runOnce()
|
|||||||
|
|
||||||
// Once we get a location we no longer desperately want an update
|
// Once we get a location we no longer desperately want an update
|
||||||
if ((gotLoc && gotTime) || tooLong) {
|
if ((gotLoc && gotTime) || tooLong) {
|
||||||
|
if (tooLong && !gotLoc) {
|
||||||
if (tooLong) {
|
|
||||||
// we didn't get a location during this ack window, therefore declare loss of lock
|
// we didn't get a location during this ack window, therefore declare loss of lock
|
||||||
if (hasValidLocation) {
|
if (hasValidLocation) {
|
||||||
LOG_DEBUG("hasValidLocation FALLING EDGE");
|
LOG_DEBUG("hasValidLocation FALLING EDGE");
|
||||||
@@ -1143,9 +1144,15 @@ int32_t GPS::runOnce()
|
|||||||
p = meshtastic_Position_init_default;
|
p = meshtastic_Position_init_default;
|
||||||
hasValidLocation = false;
|
hasValidLocation = false;
|
||||||
}
|
}
|
||||||
|
if (millis() > fixHoldEnds) {
|
||||||
down();
|
shouldPublish = true; // publish our update at the end of the lock hold
|
||||||
shouldPublish = true; // publish our update for this just finished acquisition window
|
publishUpdate();
|
||||||
|
down();
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
} else {
|
||||||
|
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
|
// If state has changed do a publish
|
||||||
@@ -1198,7 +1205,7 @@ static const char *DETECTED_MESSAGE = "%s detected";
|
|||||||
LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \
|
LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \
|
||||||
clearBuffer(); \
|
clearBuffer(); \
|
||||||
_serial_gps->write(COMMAND "\r\n"); \
|
_serial_gps->write(COMMAND "\r\n"); \
|
||||||
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP); \
|
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP, serialSpeed); \
|
||||||
if (detectedDriver != GNSS_MODEL_UNKNOWN) { \
|
if (detectedDriver != GNSS_MODEL_UNKNOWN) { \
|
||||||
return detectedDriver; \
|
return detectedDriver; \
|
||||||
} \
|
} \
|
||||||
@@ -1360,36 +1367,55 @@ GnssModel_t GPS::probe(int serialSpeed)
|
|||||||
return GNSS_MODEL_UNKNOWN;
|
return GNSS_MODEL_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap)
|
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap, int serialSpeed)
|
||||||
{
|
{
|
||||||
String response = "";
|
// Calculate buffer size based on baud rate - 256 bytes for 9600 baud as baseline
|
||||||
|
// Higher baud rates get proportionally larger buffers to handle more data
|
||||||
|
int bufferSize = (serialSpeed * 256) / 9600;
|
||||||
|
// Clamp buffer size between reasonable limits
|
||||||
|
if (bufferSize < 128)
|
||||||
|
bufferSize = 128;
|
||||||
|
if (bufferSize > 2048)
|
||||||
|
bufferSize = 2048;
|
||||||
|
|
||||||
|
char *response = new char[bufferSize](); // Dynamically allocate based on baud rate
|
||||||
|
uint16_t responseLen = 0;
|
||||||
unsigned long start = millis();
|
unsigned long start = millis();
|
||||||
while (millis() - start < timeout) {
|
while (millis() - start < timeout) {
|
||||||
if (_serial_gps->available()) {
|
if (_serial_gps->available()) {
|
||||||
response += (char)_serial_gps->read();
|
char c = _serial_gps->read();
|
||||||
|
|
||||||
if (response.endsWith(",") || response.endsWith("\r\n")) {
|
// Add char to buffer if there's space
|
||||||
|
if (responseLen < bufferSize - 1) {
|
||||||
|
response[responseLen++] = c;
|
||||||
|
response[responseLen] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
|
||||||
#ifdef GPS_DEBUG
|
#ifdef GPS_DEBUG
|
||||||
LOG_DEBUG(response.c_str());
|
LOG_DEBUG(response);
|
||||||
#endif
|
#endif
|
||||||
// check if we can see our chips
|
// check if we can see our chips
|
||||||
for (const auto &chipInfo : responseMap) {
|
for (const auto &chipInfo : responseMap) {
|
||||||
if (strstr(response.c_str(), chipInfo.detectionString.c_str()) != nullptr) {
|
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
|
||||||
LOG_INFO("%s detected", chipInfo.chipName.c_str());
|
LOG_INFO("%s detected", chipInfo.chipName.c_str());
|
||||||
|
delete[] response; // Cleanup before return
|
||||||
return chipInfo.driver;
|
return chipInfo.driver;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (response.endsWith("\r\n")) {
|
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
|
||||||
response.trim();
|
// Reset the response buffer for the next potential message
|
||||||
response = ""; // Reset the response string for the next potential message
|
responseLen = 0;
|
||||||
|
response[0] = '\0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef GPS_DEBUG
|
#ifdef GPS_DEBUG
|
||||||
LOG_DEBUG(response.c_str());
|
LOG_DEBUG(response);
|
||||||
#endif
|
#endif
|
||||||
return GNSS_MODEL_UNKNOWN; // Return empty string on timeout
|
delete[] response; // Cleanup before return
|
||||||
|
return GNSS_MODEL_UNKNOWN; // Return unknown on timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
GPS *GPS::createGps()
|
GPS *GPS::createGps()
|
||||||
@@ -1504,28 +1530,10 @@ static int32_t toDegInt(RawDegrees d)
|
|||||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
* Override this method to check for new locations
|
* Override this method to check for new locations
|
||||||
*
|
*
|
||||||
* @return true if we've acquired a new location
|
* @return true if we've set a new time
|
||||||
*/
|
*/
|
||||||
bool GPS::lookForTime()
|
bool GPS::lookForTime()
|
||||||
{
|
{
|
||||||
|
|
||||||
#ifdef GNSS_AIROHA
|
|
||||||
uint8_t fix = reader.fixQuality();
|
|
||||||
if (fix >= 1 && fix <= 5) {
|
|
||||||
if (lastFixStartMsec > 0) {
|
|
||||||
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
clearBuffer();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lastFixStartMsec = millis();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
auto ti = reader.time;
|
auto ti = reader.time;
|
||||||
auto d = reader.date;
|
auto d = reader.date;
|
||||||
if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed
|
if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed
|
||||||
@@ -1542,13 +1550,13 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
|||||||
t.tm_year = d.year() - 1900;
|
t.tm_year = d.year() - 1900;
|
||||||
t.tm_isdst = false;
|
t.tm_isdst = false;
|
||||||
if (t.tm_mon > -1) {
|
if (t.tm_mon > -1) {
|
||||||
LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min,
|
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultSuccess) {
|
||||||
t.tm_sec, ti.age());
|
LOG_DEBUG("NMEA GPS time set %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour,
|
||||||
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) {
|
t.tm_min, t.tm_sec, ti.age());
|
||||||
// Clear the GPS buffer if we got an invalid time
|
return true;
|
||||||
clearBuffer();
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
} else
|
} else
|
||||||
@@ -1563,25 +1571,6 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
|||||||
*/
|
*/
|
||||||
bool GPS::lookForLocation()
|
bool GPS::lookForLocation()
|
||||||
{
|
{
|
||||||
#ifdef GNSS_AIROHA
|
|
||||||
if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) {
|
|
||||||
uint8_t fix = reader.fixQuality();
|
|
||||||
if (fix >= 1 && fix <= 5) {
|
|
||||||
if (lastFixStartMsec > 0) {
|
|
||||||
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
clearBuffer();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lastFixStartMsec = millis();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// By default, TinyGPS++ does not parse GPGSA lines, which give us
|
// By default, TinyGPS++ does not parse GPGSA lines, which give us
|
||||||
// the 2D/3D fixType (see NMEAGPS.h)
|
// the 2D/3D fixType (see NMEAGPS.h)
|
||||||
// At a minimum, use the fixQuality indicator in GPGGA (FIXME?)
|
// At a minimum, use the fixQuality indicator in GPGGA (FIXME?)
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class GPS : private concurrency::OSThread
|
|||||||
uint8_t fixType = 0; // fix type from GPGSA
|
uint8_t fixType = 0; // fix type from GPGSA
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0;
|
uint32_t fixHoldEnds = 0;
|
||||||
uint32_t rx_gpio = 0;
|
uint32_t rx_gpio = 0;
|
||||||
uint32_t tx_gpio = 0;
|
uint32_t tx_gpio = 0;
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ class GPS : private concurrency::OSThread
|
|||||||
|
|
||||||
virtual int32_t runOnce() override;
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap);
|
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap, int serialSpeed);
|
||||||
|
|
||||||
// Get GNSS model
|
// Get GNSS model
|
||||||
GnssModel_t probe(int serialSpeed);
|
GnssModel_t probe(int serialSpeed);
|
||||||
|
|||||||
112
src/gps/RTC.cpp
112
src/gps/RTC.cpp
@@ -9,6 +9,9 @@
|
|||||||
static RTCQuality currentQuality = RTCQualityNone;
|
static RTCQuality currentQuality = RTCQualityNone;
|
||||||
uint32_t lastSetFromPhoneNtpOrGps = 0;
|
uint32_t lastSetFromPhoneNtpOrGps = 0;
|
||||||
|
|
||||||
|
static uint32_t lastTimeValidationWarning = 0;
|
||||||
|
static const uint32_t TIME_VALIDATION_WARNING_INTERVAL_MS = 15000; // 15 seconds
|
||||||
|
|
||||||
RTCQuality getRTCQuality()
|
RTCQuality getRTCQuality()
|
||||||
{
|
{
|
||||||
return currentQuality;
|
return currentQuality;
|
||||||
@@ -23,7 +26,7 @@ static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only upda
|
|||||||
* Reads the current date and time from the RTC module and updates the system time.
|
* Reads the current date and time from the RTC module and updates the system time.
|
||||||
* @return True if the RTC was successfully read and the system time was updated, false otherwise.
|
* @return True if the RTC was successfully read and the system time was updated, false otherwise.
|
||||||
*/
|
*/
|
||||||
void readFromRTC()
|
RTCSetResult readFromRTC()
|
||||||
{
|
{
|
||||||
struct timeval tv; /* btw settimeofday() is helpful here too*/
|
struct timeval tv; /* btw settimeofday() is helpful here too*/
|
||||||
#ifdef RV3028_RTC
|
#ifdef RV3028_RTC
|
||||||
@@ -44,15 +47,25 @@ void readFromRTC()
|
|||||||
t.tm_sec = rtc.getSecond();
|
t.tm_sec = rtc.getSecond();
|
||||||
tv.tv_sec = gm_mktime(&t);
|
tv.tv_sec = gm_mktime(&t);
|
||||||
tv.tv_usec = 0;
|
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
|
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
return RTCSetResultInvalidTime;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
|
LOG_DEBUG("Read RTC time from RV3028 getTime 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);
|
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
||||||
timeStartMsec = now;
|
|
||||||
zeroOffsetSecs = tv.tv_sec;
|
|
||||||
if (currentQuality == RTCQualityNone) {
|
if (currentQuality == RTCQualityNone) {
|
||||||
|
timeStartMsec = now;
|
||||||
|
zeroOffsetSecs = tv.tv_sec;
|
||||||
currentQuality = RTCQualityDevice;
|
currentQuality = RTCQualityDevice;
|
||||||
}
|
}
|
||||||
|
return RTCSetResultSuccess;
|
||||||
}
|
}
|
||||||
#elif defined(PCF8563_RTC)
|
#elif defined(PCF8563_RTC)
|
||||||
if (rtc_found.address == PCF8563_RTC) {
|
if (rtc_found.address == PCF8563_RTC) {
|
||||||
@@ -75,15 +88,26 @@ void readFromRTC()
|
|||||||
t.tm_sec = tc.second;
|
t.tm_sec = tc.second;
|
||||||
tv.tv_sec = gm_mktime(&t);
|
tv.tv_sec = gm_mktime(&t);
|
||||||
tv.tv_usec = 0;
|
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
|
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
|
LOG_DEBUG("Read RTC time from PCF8563 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);
|
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
||||||
timeStartMsec = now;
|
|
||||||
zeroOffsetSecs = tv.tv_sec;
|
|
||||||
if (currentQuality == RTCQualityNone) {
|
if (currentQuality == RTCQualityNone) {
|
||||||
|
timeStartMsec = now;
|
||||||
|
zeroOffsetSecs = tv.tv_sec;
|
||||||
currentQuality = RTCQualityDevice;
|
currentQuality = RTCQualityDevice;
|
||||||
}
|
}
|
||||||
|
return RTCSetResultSuccess;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (!gettimeofday(&tv, NULL)) {
|
if (!gettimeofday(&tv, NULL)) {
|
||||||
@@ -92,8 +116,10 @@ void readFromRTC()
|
|||||||
LOG_DEBUG("Read RTC time as %ld", printableEpoch);
|
LOG_DEBUG("Read RTC time as %ld", printableEpoch);
|
||||||
timeStartMsec = now;
|
timeStartMsec = now;
|
||||||
zeroOffsetSecs = tv.tv_sec;
|
zeroOffsetSecs = tv.tv_sec;
|
||||||
|
return RTCSetResultSuccess;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
return RTCSetResultNotSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,7 +127,7 @@ void readFromRTC()
|
|||||||
*
|
*
|
||||||
* @param q The quality of the provided time.
|
* @param q The quality of the provided time.
|
||||||
* @param tv A pointer to a timeval struct containing the time to potentially set the RTC to.
|
* @param tv A pointer to a timeval struct containing the time to potentially set the RTC to.
|
||||||
* @return True if the RTC was set, false otherwise.
|
* @return RTCSetResult
|
||||||
*
|
*
|
||||||
* If we haven't yet set our RTC this boot, set it from a GPS derived time
|
* If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||||
*/
|
*/
|
||||||
@@ -112,7 +138,20 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
|
|||||||
uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||||
#ifdef BUILD_EPOCH
|
#ifdef BUILD_EPOCH
|
||||||
if (tv->tv_sec < BUILD_EPOCH) {
|
if (tv->tv_sec < BUILD_EPOCH) {
|
||||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, 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;
|
||||||
|
} else if ((uint64_t)tv->tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) {
|
||||||
|
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
|
||||||
|
// Calculate max allowed time safely to avoid overflow in logging
|
||||||
|
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
|
||||||
|
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
|
||||||
|
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
|
||||||
|
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
|
||||||
|
lastTimeValidationWarning = millis();
|
||||||
|
}
|
||||||
return RTCSetResultInvalidTime;
|
return RTCSetResultInvalidTime;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -230,7 +269,20 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
|
|||||||
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||||
#ifdef BUILD_EPOCH
|
#ifdef BUILD_EPOCH
|
||||||
if (tv.tv_sec < BUILD_EPOCH) {
|
if (tv.tv_sec < BUILD_EPOCH) {
|
||||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, 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;
|
||||||
|
} else if ((uint64_t)tv.tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) {
|
||||||
|
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
|
||||||
|
// Calculate max allowed time safely to avoid overflow in logging
|
||||||
|
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
|
||||||
|
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
|
||||||
|
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
|
||||||
|
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
|
||||||
|
lastTimeValidationWarning = millis();
|
||||||
|
}
|
||||||
return RTCSetResultInvalidTime;
|
return RTCSetResultInvalidTime;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -290,14 +342,40 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
|
|||||||
time_t gm_mktime(struct tm *tm)
|
time_t gm_mktime(struct tm *tm)
|
||||||
{
|
{
|
||||||
#if !MESHTASTIC_EXCLUDE_TZ
|
#if !MESHTASTIC_EXCLUDE_TZ
|
||||||
setenv("TZ", "GMT0", 1);
|
time_t result = 0;
|
||||||
time_t res = mktime(tm);
|
|
||||||
if (*config.device.tzdef) {
|
// First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
|
||||||
setenv("TZ", config.device.tzdef, 1);
|
int year = 1900 + tm->tm_year; // tm_year is years since 1900
|
||||||
} else {
|
int year_minus_one = year - 1;
|
||||||
setenv("TZ", "UTC0", 1);
|
int days_before_this_year = 0;
|
||||||
|
days_before_this_year += year_minus_one * 365;
|
||||||
|
// leap days: every 4 years, except 100s, but including 400s.
|
||||||
|
days_before_this_year += year_minus_one / 4 - year_minus_one / 100 + year_minus_one / 400;
|
||||||
|
// subtract from 1970-01-01 to get days since epoch
|
||||||
|
days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400);
|
||||||
|
|
||||||
|
// Now, within this tm->year, compute the days *before* this tm->month starts.
|
||||||
|
int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
|
||||||
|
int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11
|
||||||
|
|
||||||
|
// If this is a leap year, and we're past February, add a day:
|
||||||
|
if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
|
||||||
|
days_this_year_before_this_month += 1;
|
||||||
}
|
}
|
||||||
return res;
|
|
||||||
|
// And within this month:
|
||||||
|
int days_this_month_before_today = tm->tm_mday - 1; // tm->tm_mday is 1..31
|
||||||
|
|
||||||
|
// Now combine them all together, and convert days to seconds:
|
||||||
|
result += (days_before_this_year + days_this_year_before_this_month + days_this_month_before_today);
|
||||||
|
result *= 86400L;
|
||||||
|
|
||||||
|
// Finally, add in the hours, minutes, and seconds of today:
|
||||||
|
result += tm->tm_hour * 3600;
|
||||||
|
result += tm->tm_min * 60;
|
||||||
|
result += tm->tm_sec;
|
||||||
|
|
||||||
|
return result;
|
||||||
#else
|
#else
|
||||||
return mktime(tm);
|
return mktime(tm);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -48,10 +48,13 @@ uint32_t getTime(bool local = false);
|
|||||||
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
|
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
|
||||||
uint32_t getValidTime(RTCQuality minQuality, bool local = false);
|
uint32_t getValidTime(RTCQuality minQuality, bool local = false);
|
||||||
|
|
||||||
void readFromRTC();
|
RTCSetResult readFromRTC();
|
||||||
|
|
||||||
time_t gm_mktime(struct tm *tm);
|
time_t gm_mktime(struct tm *tm);
|
||||||
|
|
||||||
#define SEC_PER_DAY 86400
|
#define SEC_PER_DAY 86400
|
||||||
#define SEC_PER_HOUR 3600
|
#define SEC_PER_HOUR 3600
|
||||||
#define SEC_PER_MIN 60
|
#define SEC_PER_MIN 60
|
||||||
|
#ifdef BUILD_EPOCH
|
||||||
|
static constexpr uint64_t FORTY_YEARS = (40ULL * 365 * SEC_PER_DAY); // Use 64-bit arithmetic to prevent overflow
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -67,20 +67,28 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
|||||||
|
|
||||||
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||||
const bool flipped = config.display.flip_screen;
|
const bool flipped = config.display.flip_screen;
|
||||||
|
// HACK for L1 EInk
|
||||||
|
#if defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||||
|
// For SEEED_WIO_TRACKER_L1_EINK, setRotation(3) is correct but mirrored; flip both axes
|
||||||
|
for (uint32_t y = 0; y < displayHeight; y++) {
|
||||||
|
for (uint32_t x = 0; x < displayWidth; x++) {
|
||||||
|
auto b = buffer[x + (y / 8) * displayWidth];
|
||||||
|
auto isset = b & (1 << (y & 7));
|
||||||
|
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
for (uint32_t y = 0; y < displayHeight; y++) {
|
for (uint32_t y = 0; y < displayHeight; y++) {
|
||||||
for (uint32_t x = 0; x < displayWidth; x++) {
|
for (uint32_t x = 0; x < displayWidth; x++) {
|
||||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient
|
|
||||||
auto b = buffer[x + (y / 8) * displayWidth];
|
auto b = buffer[x + (y / 8) * displayWidth];
|
||||||
auto isset = b & (1 << (y & 7));
|
auto isset = b & (1 << (y & 7));
|
||||||
|
|
||||||
// Handle flip here, rather than with setRotation(),
|
|
||||||
// Avoids issues when display width is not a multiple of 8
|
|
||||||
if (flipped)
|
if (flipped)
|
||||||
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
||||||
else
|
else
|
||||||
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Trigger the refresh in GxEPD2
|
// Trigger the refresh in GxEPD2
|
||||||
LOG_DEBUG("Update E-Paper");
|
LOG_DEBUG("Update E-Paper");
|
||||||
@@ -235,7 +243,7 @@ bool EInkDisplay::connect()
|
|||||||
adafruitDisplay->setRotation(1);
|
adafruitDisplay->setRotation(1);
|
||||||
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||||
}
|
}
|
||||||
#elif defined(HELTEC_MESH_POCKET)
|
#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||||
{
|
{
|
||||||
spi1 = &SPI1;
|
spi1 = &SPI1;
|
||||||
spi1->begin();
|
spi1->begin();
|
||||||
@@ -249,6 +257,7 @@ bool EInkDisplay::connect()
|
|||||||
// Init GxEPD2
|
// Init GxEPD2
|
||||||
adafruitDisplay->init();
|
adafruitDisplay->init();
|
||||||
adafruitDisplay->setRotation(3);
|
adafruitDisplay->setRotation(3);
|
||||||
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||||
}
|
}
|
||||||
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
|
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
SPIClass *hspi = NULL;
|
SPIClass *hspi = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(HELTEC_MESH_POCKET)
|
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||||
SPIClass *spi1 = NULL;
|
SPIClass *spi1 = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -317,8 +317,16 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
|||||||
#elif defined(USE_SSD1306)
|
#elif defined(USE_SSD1306)
|
||||||
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
|
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
|
||||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||||
|
#elif defined(USE_SPISSD1306)
|
||||||
|
dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48);
|
||||||
|
if (!dispdev->init()) {
|
||||||
|
LOG_DEBUG("Error: SSD1306 not detected!");
|
||||||
|
} else {
|
||||||
|
static_cast<SSD1306Spi *>(dispdev)->setHorizontalOffset(32);
|
||||||
|
LOG_INFO("SSD1306 init success");
|
||||||
|
}
|
||||||
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
|
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
|
||||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS)
|
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
|
||||||
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
||||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||||
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
|
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
|
||||||
@@ -507,7 +515,7 @@ void Screen::setup()
|
|||||||
// === Apply loaded brightness ===
|
// === Apply loaded brightness ===
|
||||||
#if defined(ST7789_CS)
|
#if defined(ST7789_CS)
|
||||||
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
|
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
|
||||||
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
|
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306)
|
||||||
dispdev->setBrightness(brightness);
|
dispdev->setBrightness(brightness);
|
||||||
#endif
|
#endif
|
||||||
LOG_INFO("Applied screen brightness: %d", brightness);
|
LOG_INFO("Applied screen brightness: %d", brightness);
|
||||||
@@ -550,11 +558,11 @@ void Screen::setup()
|
|||||||
#else
|
#else
|
||||||
if (!config.display.flip_screen) {
|
if (!config.display.flip_screen) {
|
||||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
|
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
|
||||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS)
|
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
|
||||||
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
|
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
|
||||||
#elif defined(USE_ST7789)
|
#elif defined(USE_ST7789)
|
||||||
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
|
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
|
||||||
#else
|
#elif !defined(M5STACK_UNITC6L)
|
||||||
dispdev->flipScreenVertically();
|
dispdev->flipScreenVertically();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -692,7 +700,11 @@ int32_t Screen::runOnce()
|
|||||||
|
|
||||||
#ifndef DISABLE_WELCOME_UNSET
|
#ifndef DISABLE_WELCOME_UNSET
|
||||||
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
menuHandler::LoraRegionPicker();
|
||||||
|
#else
|
||||||
menuHandler::OnboardMessage();
|
menuHandler::OnboardMessage();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
|
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
|
||||||
@@ -890,8 +902,12 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
|
|
||||||
#if defined(DISPLAY_CLOCK_FRAME)
|
#if defined(DISPLAY_CLOCK_FRAME)
|
||||||
fsi.positions.clock = numframes;
|
fsi.positions.clock = numframes;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
|
||||||
|
#else
|
||||||
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
|
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
|
||||||
: graphics::ClockRenderer::drawDigitalClockFrame;
|
: graphics::ClockRenderer::drawDigitalClockFrame;
|
||||||
|
#endif
|
||||||
indicatorIcons.push_back(digital_icon_clock);
|
indicatorIcons.push_back(digital_icon_clock);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1226,6 +1242,10 @@ void Screen::handleShowNextFrame()
|
|||||||
|
|
||||||
void Screen::setFastFramerate()
|
void Screen::setFastFramerate()
|
||||||
{
|
{
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
dispdev->clear();
|
||||||
|
dispdev->display();
|
||||||
|
#endif
|
||||||
// We are about to start a transition so speed up fps
|
// We are about to start a transition so speed up fps
|
||||||
targetFramerate = SCREEN_TRANSITION_FRAMERATE;
|
targetFramerate = SCREEN_TRANSITION_FRAMERATE;
|
||||||
|
|
||||||
@@ -1297,13 +1317,23 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (longName && longName[0]) {
|
if (longName && longName[0]) {
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
strcpy(banner, "New Message");
|
||||||
|
#else
|
||||||
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
|
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
|
||||||
|
#endif
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
strcpy(banner, "New Message");
|
strcpy(banner, "New Message");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
screen->setOn(true);
|
||||||
|
screen->showSimpleBanner(banner, 1500);
|
||||||
|
playLongBeep();
|
||||||
|
#else
|
||||||
screen->showSimpleBanner(banner, 3000);
|
screen->showSimpleBanner(banner, 3000);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1386,7 +1416,11 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
if (devicestate.rx_text_message.from) {
|
if (devicestate.rx_text_message.from) {
|
||||||
menuHandler::messageResponseMenu();
|
menuHandler::messageResponseMenu();
|
||||||
} else {
|
} else {
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
menuHandler::textMessageMenu();
|
||||||
|
#else
|
||||||
menuHandler::textMessageBaseMenu();
|
menuHandler::textMessageBaseMenu();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
} else if (framesetInfo.positions.firstFavorite != 255 &&
|
} else if (framesetInfo.positions.firstFavorite != 255 &&
|
||||||
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
|
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ class Screen
|
|||||||
#include <SSD1306Wire.h>
|
#include <SSD1306Wire.h>
|
||||||
#elif defined(USE_ST7789)
|
#elif defined(USE_ST7789)
|
||||||
#include <ST7789Spi.h>
|
#include <ST7789Spi.h>
|
||||||
|
#elif defined(USE_SPISSD1306)
|
||||||
|
#include <SSD1306Spi.h>
|
||||||
#else
|
#else
|
||||||
// the SH1106/SSD1306 variant is auto-detected
|
// the SH1106/SSD1306 variant is auto-detected
|
||||||
#include <AutoOLEDWire.h>
|
#include <AutoOLEDWire.h>
|
||||||
|
|||||||
@@ -73,12 +73,16 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS)) && \
|
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
// The screen is bigger so use bigger fonts
|
// The screen is bigger so use bigger fonts
|
||||||
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
|
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
|
||||||
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
|
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
|
||||||
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
|
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
|
||||||
|
#elif defined(M5STACK_UNITC6L)
|
||||||
|
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
|
||||||
|
#define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13
|
||||||
|
#define FONT_LARGE FONT_SMALL_LOCAL // Height: 13
|
||||||
#else
|
#else
|
||||||
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
|
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
|
||||||
#define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19
|
#define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
|
|
||||||
int batteryX = 1;
|
int batteryX = 1;
|
||||||
int batteryY = HEADER_OFFSET_Y + 1;
|
int batteryY = HEADER_OFFSET_Y + 1;
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
// === Battery Icons ===
|
// === Battery Icons ===
|
||||||
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
|
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
|
||||||
batteryX += 1;
|
batteryX += 1;
|
||||||
@@ -337,7 +337,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
display->setColor(WHITE); // Reset for other UI
|
display->setColor(WHITE); // Reset for other UI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -562,6 +562,91 @@ class LGFX : public lgfx::LGFX_Device
|
|||||||
|
|
||||||
static LGFX *tft = nullptr;
|
static LGFX *tft = nullptr;
|
||||||
|
|
||||||
|
#elif defined(ST7796_CS)
|
||||||
|
#include <LovyanGFX.hpp> // Graphics and font library for ST7796 driver chip
|
||||||
|
|
||||||
|
class LGFX : public lgfx::LGFX_Device
|
||||||
|
{
|
||||||
|
lgfx::Panel_ST7796 _panel_instance;
|
||||||
|
lgfx::Bus_SPI _bus_instance;
|
||||||
|
lgfx::Light_PWM _light_instance;
|
||||||
|
|
||||||
|
public:
|
||||||
|
LGFX(void)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto cfg = _bus_instance.config();
|
||||||
|
|
||||||
|
// SPI
|
||||||
|
cfg.spi_host = ST7796_SPI_HOST;
|
||||||
|
cfg.spi_mode = 0;
|
||||||
|
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
|
||||||
|
// 80MHz by an integer)
|
||||||
|
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
|
||||||
|
cfg.spi_3wire = false;
|
||||||
|
cfg.use_lock = true; // Set to true to use transaction locking
|
||||||
|
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
|
||||||
|
// SPI_DMA_CH_AUTO=auto setting)
|
||||||
|
cfg.pin_sclk = ST7796_SCK; // Set SPI SCLK pin number
|
||||||
|
cfg.pin_mosi = ST7796_SDA; // Set SPI MOSI pin number
|
||||||
|
cfg.pin_miso = ST7796_MISO; // Set SPI MISO pin number (-1 = disable)
|
||||||
|
cfg.pin_dc = ST7796_RS; // Set SPI DC pin number (-1 = disable)
|
||||||
|
|
||||||
|
_bus_instance.config(cfg); // applies the set value to the bus.
|
||||||
|
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Set the display panel control.
|
||||||
|
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||||||
|
|
||||||
|
cfg.pin_cs = ST7796_CS; // Pin number where CS is connected (-1 = disable)
|
||||||
|
cfg.pin_rst = ST7796_RESET; // Pin number where RST is connected (-1 = disable)
|
||||||
|
cfg.pin_busy = ST7796_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||||||
|
|
||||||
|
// cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||||||
|
// cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||||||
|
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||||||
|
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||||||
|
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||||||
|
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||||||
|
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||||||
|
#ifdef TFT_DUMMY_READ_PIXELS
|
||||||
|
cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout
|
||||||
|
#else
|
||||||
|
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
|
||||||
|
#endif
|
||||||
|
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
|
||||||
|
cfg.readable = true; // Set to true if data can be read
|
||||||
|
cfg.invert = true; // Set to true if the light/darkness of the panel is reversed
|
||||||
|
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
|
||||||
|
cfg.dlen_16bit =
|
||||||
|
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
|
||||||
|
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
|
||||||
|
|
||||||
|
_panel_instance.config(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ST7796_BL
|
||||||
|
// Set the backlight control. (delete if not necessary)
|
||||||
|
{
|
||||||
|
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
|
||||||
|
|
||||||
|
cfg.pin_bl = ST7796_BL; // Pin number to which the backlight is connected
|
||||||
|
cfg.invert = false; // true to invert the brightness of the backlight
|
||||||
|
cfg.freq = 44100;
|
||||||
|
cfg.pwm_channel = 7;
|
||||||
|
|
||||||
|
_light_instance.config(cfg);
|
||||||
|
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
setPanel(&_panel_instance); // Sets the panel to use.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static LGFX *tft = nullptr;
|
||||||
|
|
||||||
#elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER)
|
#elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER)
|
||||||
|
|
||||||
#include <LovyanGFX.hpp> // Graphics and font library for ILI9341/ILI9342 driver chip
|
#include <LovyanGFX.hpp> // Graphics and font library for ILI9341/ILI9342 driver chip
|
||||||
@@ -997,8 +1082,9 @@ static LGFX *tft = nullptr;
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
|
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \
|
||||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || (ARCH_PORTDUINO && HAS_SCREEN != 0)
|
defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \
|
||||||
|
(ARCH_PORTDUINO && HAS_SCREEN != 0)
|
||||||
#include "SPILock.h"
|
#include "SPILock.h"
|
||||||
#include "TFTDisplay.h"
|
#include "TFTDisplay.h"
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
@@ -1042,37 +1128,111 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TFTDisplay::~TFTDisplay()
|
||||||
|
{
|
||||||
|
// Clean up allocated line pixel buffer to prevent memory leak
|
||||||
|
if (linePixelBuffer != nullptr) {
|
||||||
|
free(linePixelBuffer);
|
||||||
|
linePixelBuffer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Write the buffer to the display memory
|
// Write the buffer to the display memory
|
||||||
void TFTDisplay::display(bool fromBlank)
|
void TFTDisplay::display(bool fromBlank)
|
||||||
{
|
{
|
||||||
if (fromBlank)
|
if (fromBlank)
|
||||||
tft->fillScreen(TFT_BLACK);
|
tft->fillScreen(TFT_BLACK);
|
||||||
// tft->clear();
|
|
||||||
concurrency::LockGuard g(spiLock);
|
concurrency::LockGuard g(spiLock);
|
||||||
|
|
||||||
uint16_t x, y;
|
uint32_t x, y;
|
||||||
|
uint32_t y_byteIndex;
|
||||||
|
uint8_t y_byteMask;
|
||||||
|
uint32_t x_FirstPixelUpdate;
|
||||||
|
uint32_t x_LastPixelUpdate;
|
||||||
|
bool isset, dblbuf_isset;
|
||||||
|
uint16_t colorTftMesh, colorTftBlack;
|
||||||
|
bool somethingChanged = false;
|
||||||
|
|
||||||
for (y = 0; y < displayHeight; y++) {
|
// Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step
|
||||||
for (x = 0; x < displayWidth; x++) {
|
colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8);
|
||||||
auto isset = buffer[x + (y / 8) * displayWidth] & (1 << (y & 7));
|
colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8);
|
||||||
|
|
||||||
|
y = 0;
|
||||||
|
while (y < displayHeight) {
|
||||||
|
y_byteIndex = (y / 8) * displayWidth;
|
||||||
|
y_byteMask = (1 << (y & 7));
|
||||||
|
|
||||||
|
// Step 1: Do a quick scan of 8 rows together. This allows fast-forwarding over unchanged screen areas.
|
||||||
|
if (y_byteMask == 1) {
|
||||||
if (!fromBlank) {
|
if (!fromBlank) {
|
||||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
for (x = 0; x < displayWidth; x++) {
|
||||||
auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7));
|
if (buffer[x + y_byteIndex] != buffer_back[x + y_byteIndex])
|
||||||
if (isset != dblbuf_isset) {
|
break;
|
||||||
tft->drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK);
|
|
||||||
}
|
}
|
||||||
} else if (isset) {
|
} else {
|
||||||
tft->drawPixel(x, y, TFT_MESH);
|
for (x = 0; x < displayWidth; x++) {
|
||||||
|
if (buffer[x + y_byteIndex] != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (x >= displayWidth) {
|
||||||
|
// No changed pixels found in these 8 rows, fast-forward to the next 8
|
||||||
|
y = y + 8;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 2: Scan each of the 8 rows individually. Find the first pixel in each row that needs updating
|
||||||
|
for (x_FirstPixelUpdate = 0; x_FirstPixelUpdate < displayWidth; x_FirstPixelUpdate++) {
|
||||||
|
isset = buffer[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
|
||||||
|
|
||||||
|
if (!fromBlank) {
|
||||||
|
// get src pixel in the page based ordering the OLED lib uses
|
||||||
|
dblbuf_isset = buffer_back[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
|
||||||
|
if (isset != dblbuf_isset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (isset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did we find a pixel that needs updating on this row?
|
||||||
|
if (x_FirstPixelUpdate < displayWidth) {
|
||||||
|
|
||||||
|
// Quickly write out the first changed pixel (saves another array lookup)
|
||||||
|
linePixelBuffer[x_FirstPixelUpdate] = isset ? colorTftMesh : colorTftBlack;
|
||||||
|
x_LastPixelUpdate = x_FirstPixelUpdate;
|
||||||
|
|
||||||
|
// Step 3: copy all remaining pixels in this row into the pixel line buffer,
|
||||||
|
// while also recording the last pixel in the row that needs updating
|
||||||
|
for (x = x_FirstPixelUpdate + 1; x < displayWidth; x++) {
|
||||||
|
isset = buffer[x + y_byteIndex] & y_byteMask;
|
||||||
|
linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack;
|
||||||
|
|
||||||
|
if (!fromBlank) {
|
||||||
|
dblbuf_isset = buffer_back[x + y_byteIndex] & y_byteMask;
|
||||||
|
if (isset != dblbuf_isset) {
|
||||||
|
x_LastPixelUpdate = x;
|
||||||
|
}
|
||||||
|
} else if (isset) {
|
||||||
|
x_LastPixelUpdate = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
|
||||||
|
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
|
||||||
|
tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
|
||||||
|
&linePixelBuffer[x_FirstPixelUpdate]);
|
||||||
|
|
||||||
|
somethingChanged = true;
|
||||||
|
}
|
||||||
|
y++;
|
||||||
}
|
}
|
||||||
// Copy the Buffer to the Back Buffer
|
// Copy the Buffer to the Back Buffer
|
||||||
for (y = 0; y < (displayHeight / 8); y++) {
|
if (somethingChanged)
|
||||||
for (x = 0; x < displayWidth; x++) {
|
memcpy(buffer_back, buffer, displayBufferSize);
|
||||||
uint16_t pos = x + y * displayWidth;
|
|
||||||
buffer_back[pos] = buffer[pos];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TFTDisplay::sdlLoop()
|
void TFTDisplay::sdlLoop()
|
||||||
@@ -1264,13 +1424,21 @@ bool TFTDisplay::connect()
|
|||||||
tft->setRotation(1); // T-Deck has the TFT in landscape
|
tft->setRotation(1); // T-Deck has the TFT in landscape
|
||||||
#elif defined(T_WATCH_S3)
|
#elif defined(T_WATCH_S3)
|
||||||
tft->setRotation(2); // T-Watch S3 left-handed orientation
|
tft->setRotation(2); // T-Watch S3 left-handed orientation
|
||||||
#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR)
|
#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) || defined(T_LORA_PAGER)
|
||||||
tft->setRotation(0); // use config.yaml to set rotation
|
tft->setRotation(0); // use config.yaml to set rotation
|
||||||
#else
|
#else
|
||||||
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
||||||
#endif
|
#endif
|
||||||
tft->fillScreen(TFT_BLACK);
|
tft->fillScreen(TFT_BLACK);
|
||||||
|
|
||||||
|
if (this->linePixelBuffer == NULL) {
|
||||||
|
this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth);
|
||||||
|
|
||||||
|
if (!this->linePixelBuffer) {
|
||||||
|
LOG_ERROR("Not enough memory to create TFT line buffer\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ class TFTDisplay : public OLEDDisplay
|
|||||||
*/
|
*/
|
||||||
TFTDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C);
|
TFTDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C);
|
||||||
|
|
||||||
|
// Destructor to clean up allocated memory
|
||||||
|
~TFTDisplay();
|
||||||
|
|
||||||
// Write the buffer to the display memory
|
// Write the buffer to the display memory
|
||||||
virtual void display() override { display(false); };
|
virtual void display() override { display(false); };
|
||||||
virtual void display(bool fromBlank);
|
virtual void display(bool fromBlank);
|
||||||
@@ -58,4 +61,6 @@ class TFTDisplay : public OLEDDisplay
|
|||||||
|
|
||||||
// Connect to the display
|
// Connect to the display
|
||||||
virtual bool connect() override;
|
virtual bool connect() override;
|
||||||
|
|
||||||
|
uint16_t *linePixelBuffer = nullptr;
|
||||||
};
|
};
|
||||||
@@ -94,7 +94,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
|
|||||||
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
|
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
|
||||||
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
|
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
|
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
|
||||||
|
ARCH_PORTDUINO) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
|
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
|
||||||
8, imgQuestionL1);
|
8, imgQuestionL1);
|
||||||
@@ -106,7 +107,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
|
|||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \
|
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
|
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
|
||||||
8, imgSFL1);
|
8, imgSFL1);
|
||||||
@@ -121,7 +122,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
|
|||||||
} else {
|
} else {
|
||||||
// TODO: Raspberry Pi supports more than just the one screen size
|
// TODO: Raspberry Pi supports more than just the one screen size
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
|
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
|
||||||
|
ARCH_PORTDUINO) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
|
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
|
||||||
imgInfoL1);
|
imgInfoL1);
|
||||||
@@ -261,12 +263,6 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
display->drawString(x + 1, y, "USB");
|
display->drawString(x + 1, y, "USB");
|
||||||
}
|
}
|
||||||
|
|
||||||
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
|
|
||||||
|
|
||||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
|
|
||||||
// if (config.display.heading_bold)
|
|
||||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
|
|
||||||
|
|
||||||
uint32_t currentMillis = millis();
|
uint32_t currentMillis = millis();
|
||||||
uint32_t seconds = currentMillis / 1000;
|
uint32_t seconds = currentMillis / 1000;
|
||||||
uint32_t minutes = seconds / 60;
|
uint32_t minutes = seconds / 60;
|
||||||
@@ -281,12 +277,13 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
|
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
|
||||||
|
|
||||||
// Line 1 (Still)
|
// Line 1 (Still)
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
||||||
if (config.display.heading_bold)
|
if (config.display.heading_bold)
|
||||||
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
||||||
|
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
|
#endif
|
||||||
// Setup string to assemble analogClock string
|
// Setup string to assemble analogClock string
|
||||||
std::string analogClock = "";
|
std::string analogClock = "";
|
||||||
|
|
||||||
@@ -390,17 +387,24 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
char shortnameble[35];
|
char shortnameble[35];
|
||||||
getMacAddr(dmac);
|
getMacAddr(dmac);
|
||||||
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
|
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
snprintf(shortnameble, sizeof(shortnameble), "%s", screen->ourId);
|
||||||
|
#else
|
||||||
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
|
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
|
||||||
|
#endif
|
||||||
int textWidth = display->getStringWidth(shortnameble);
|
int textWidth = display->getStringWidth(shortnameble);
|
||||||
int nameX = (SCREEN_WIDTH - textWidth);
|
int nameX = (SCREEN_WIDTH - textWidth);
|
||||||
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
||||||
|
|
||||||
// === Second Row: Radio Preset ===
|
// === Second Row: Radio Preset ===
|
||||||
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
|
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
|
||||||
char regionradiopreset[25];
|
char regionradiopreset[25];
|
||||||
const char *region = myRegion ? myRegion->name : NULL;
|
const char *region = myRegion ? myRegion->name : NULL;
|
||||||
if (region != nullptr) {
|
if (region != nullptr) {
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region);
|
||||||
|
#else
|
||||||
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
|
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
textWidth = display->getStringWidth(regionradiopreset);
|
textWidth = display->getStringWidth(regionradiopreset);
|
||||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
@@ -412,9 +416,17 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
float freq = RadioLibInterface::instance->getFreq();
|
float freq = RadioLibInterface::instance->getFreq();
|
||||||
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
|
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
|
||||||
if (config.lora.channel_num == 0) {
|
if (config.lora.channel_num == 0) {
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz", freqStr);
|
||||||
|
#else
|
||||||
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
|
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num);
|
||||||
|
#else
|
||||||
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
|
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
size_t len = strlen(frequencyslot);
|
size_t len = strlen(frequencyslot);
|
||||||
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
|
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
|
||||||
@@ -424,6 +436,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
|
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
|
||||||
|
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
// === Fourth Row: Channel Utilization ===
|
// === Fourth Row: Channel Utilization ===
|
||||||
const char *chUtil = "ChUtil:";
|
const char *chUtil = "ChUtil:";
|
||||||
char chUtilPercentage[10];
|
char chUtilPercentage[10];
|
||||||
@@ -480,6 +493,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
|
|
||||||
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[4],
|
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[4],
|
||||||
chUtilPercentage);
|
chUtilPercentage);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************
|
// ****************************
|
||||||
@@ -505,8 +519,11 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
barsOffset -= 12;
|
barsOffset -= 12;
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
const int barX = x + 45 + barsOffset;
|
||||||
|
#else
|
||||||
const int barX = x + 40 + barsOffset;
|
const int barX = x + 40 + barsOffset;
|
||||||
|
#endif
|
||||||
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
|
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
|
||||||
if (total == 0)
|
if (total == 0)
|
||||||
return;
|
return;
|
||||||
@@ -531,7 +548,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
// Label
|
// Label
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->drawString(labelX, getTextPositions(display)[line], label);
|
display->drawString(labelX, getTextPositions(display)[line], label);
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
// Bar
|
// Bar
|
||||||
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
|
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
@@ -539,7 +556,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
|
|
||||||
display->fillRect(barX, barY, fillWidth, barHeight);
|
display->fillRect(barX, barY, fillWidth, barHeight);
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
|
#endif
|
||||||
// Value string
|
// Value string
|
||||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||||
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
|
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
|
||||||
@@ -592,10 +609,16 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
line += 1;
|
line += 1;
|
||||||
}
|
}
|
||||||
line += 1;
|
line += 1;
|
||||||
|
|
||||||
char appversionstr[35];
|
char appversionstr[35];
|
||||||
snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION));
|
snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION));
|
||||||
char appversionstr_formatted[40];
|
char appversionstr_formatted[40];
|
||||||
char *lastDot = strrchr(appversionstr, '.');
|
char *lastDot = strrchr(appversionstr, '.');
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
if (lastDot != nullptr) {
|
||||||
|
*lastDot = '\0'; // truncate string
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (lastDot) {
|
if (lastDot) {
|
||||||
size_t prefixLen = lastDot - appversionstr;
|
size_t prefixLen = lastDot - appversionstr;
|
||||||
strncpy(appversionstr_formatted, appversionstr, prefixLen);
|
strncpy(appversionstr_formatted, appversionstr, prefixLen);
|
||||||
@@ -606,10 +629,12 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
|
strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
|
||||||
appversionstr[sizeof(appversionstr) - 1] = '\0';
|
appversionstr[sizeof(appversionstr) - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
int textWidth = display->getStringWidth(appversionstr);
|
int textWidth = display->getStringWidth(appversionstr);
|
||||||
int nameX = (SCREEN_WIDTH - textWidth) / 2;
|
int nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
|
|
||||||
|
|
||||||
|
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
|
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
|
||||||
line += 1;
|
line += 1;
|
||||||
char uptimeStr[32] = "";
|
char uptimeStr[32] = "";
|
||||||
@@ -628,6 +653,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
|
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
} // namespace DebugRenderer
|
} // namespace DebugRenderer
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
|
|||||||
@@ -79,7 +79,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
|||||||
"NP_865",
|
"NP_865",
|
||||||
"BR_902"};
|
"BR_902"};
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
bannerOptions.message = "LoRa Region";
|
||||||
|
#else
|
||||||
bannerOptions.message = "Set the LoRa region";
|
bannerOptions.message = "Set the LoRa region";
|
||||||
|
#endif
|
||||||
bannerOptions.durationMs = duration;
|
bannerOptions.durationMs = duration;
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 27;
|
bannerOptions.optionsCount = 27;
|
||||||
@@ -260,7 +264,11 @@ void menuHandler::TZPicker()
|
|||||||
|
|
||||||
void menuHandler::clockMenu()
|
void menuHandler::clockMenu()
|
||||||
{
|
{
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
static const char *optionsArray[] = {"Back", "Time Format", "Timezone"};
|
||||||
|
#else
|
||||||
static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"};
|
static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"};
|
||||||
|
#endif
|
||||||
enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 };
|
enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 };
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
bannerOptions.message = "Clock Action";
|
bannerOptions.message = "Clock Action";
|
||||||
@@ -284,8 +292,11 @@ void menuHandler::clockMenu()
|
|||||||
void menuHandler::messageResponseMenu()
|
void menuHandler::messageResponseMenu()
|
||||||
{
|
{
|
||||||
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
|
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply Preset"};
|
||||||
|
#else
|
||||||
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
|
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
|
||||||
|
#endif
|
||||||
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
|
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
|
||||||
int options = 3;
|
int options = 3;
|
||||||
|
|
||||||
@@ -299,7 +310,11 @@ void menuHandler::messageResponseMenu()
|
|||||||
optionsEnumArray[options++] = Aloud;
|
optionsEnumArray[options++] = Aloud;
|
||||||
#endif
|
#endif
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
bannerOptions.message = "Message";
|
||||||
|
#else
|
||||||
bannerOptions.message = "Message Action";
|
bannerOptions.message = "Message Action";
|
||||||
|
#endif
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||||
bannerOptions.optionsCount = options;
|
bannerOptions.optionsCount = options;
|
||||||
@@ -349,7 +364,11 @@ void menuHandler::homeBaseMenu()
|
|||||||
|
|
||||||
optionsArray[options] = "Send Position";
|
optionsArray[options] = "Send Position";
|
||||||
optionsEnumArray[options++] = Position;
|
optionsEnumArray[options++] = Position;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
optionsArray[options] = "New Preset";
|
||||||
|
#else
|
||||||
optionsArray[options] = "New Preset Msg";
|
optionsArray[options] = "New Preset Msg";
|
||||||
|
#endif
|
||||||
optionsEnumArray[options++] = Preset;
|
optionsEnumArray[options++] = Preset;
|
||||||
if (kb_found) {
|
if (kb_found) {
|
||||||
optionsArray[options] = "New Freetext Msg";
|
optionsArray[options] = "New Freetext Msg";
|
||||||
@@ -357,7 +376,11 @@ void menuHandler::homeBaseMenu()
|
|||||||
}
|
}
|
||||||
|
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
bannerOptions.message = "Home";
|
||||||
|
#else
|
||||||
bannerOptions.message = "Home Action";
|
bannerOptions.message = "Home Action";
|
||||||
|
#endif
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||||
bannerOptions.optionsCount = options;
|
bannerOptions.optionsCount = options;
|
||||||
@@ -396,6 +419,11 @@ void menuHandler::homeBaseMenu()
|
|||||||
screen->showOverlayBanner(bannerOptions);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void menuHandler::textMessageMenu()
|
||||||
|
{
|
||||||
|
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
|
||||||
|
}
|
||||||
|
|
||||||
void menuHandler::textMessageBaseMenu()
|
void menuHandler::textMessageBaseMenu()
|
||||||
{
|
{
|
||||||
enum optionsNumbers { Back, Preset, Freetext, enumEnd };
|
enum optionsNumbers { Back, Preset, Freetext, enumEnd };
|
||||||
@@ -434,16 +462,22 @@ void menuHandler::systemBaseMenu()
|
|||||||
|
|
||||||
optionsArray[options] = "Notifications";
|
optionsArray[options] = "Notifications";
|
||||||
optionsEnumArray[options++] = Notifications;
|
optionsEnumArray[options++] = Notifications;
|
||||||
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \
|
#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \
|
||||||
defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
|
defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
|
||||||
optionsArray[options] = "Screen Options";
|
optionsArray[options] = "Screen Options";
|
||||||
optionsEnumArray[options++] = ScreenOptions;
|
optionsEnumArray[options++] = ScreenOptions;
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
optionsArray[options] = "Bluetooth";
|
||||||
|
#else
|
||||||
optionsArray[options] = "Bluetooth Toggle";
|
optionsArray[options] = "Bluetooth Toggle";
|
||||||
|
#endif
|
||||||
optionsEnumArray[options++] = Bluetooth;
|
optionsEnumArray[options++] = Bluetooth;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
optionsArray[options] = "Power";
|
||||||
|
#else
|
||||||
optionsArray[options] = "Reboot/Shutdown";
|
optionsArray[options] = "Reboot/Shutdown";
|
||||||
|
#endif
|
||||||
optionsEnumArray[options++] = PowerMenu;
|
optionsEnumArray[options++] = PowerMenu;
|
||||||
|
|
||||||
if (test_enabled) {
|
if (test_enabled) {
|
||||||
@@ -452,7 +486,11 @@ void menuHandler::systemBaseMenu()
|
|||||||
}
|
}
|
||||||
|
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
bannerOptions.message = "System";
|
||||||
|
#else
|
||||||
bannerOptions.message = "System Action";
|
bannerOptions.message = "System Action";
|
||||||
|
#endif
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = options;
|
bannerOptions.optionsCount = options;
|
||||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||||
@@ -485,7 +523,11 @@ void menuHandler::systemBaseMenu()
|
|||||||
void menuHandler::favoriteBaseMenu()
|
void menuHandler::favoriteBaseMenu()
|
||||||
{
|
{
|
||||||
enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd };
|
enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd };
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
static const char *optionsArray[enumEnd] = {"Back", "New Preset"};
|
||||||
|
#else
|
||||||
static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
|
static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
|
||||||
|
#endif
|
||||||
static int optionsEnumArray[enumEnd] = {Back, Preset};
|
static int optionsEnumArray[enumEnd] = {Back, Preset};
|
||||||
int options = 2;
|
int options = 2;
|
||||||
|
|
||||||
@@ -493,13 +535,19 @@ void menuHandler::favoriteBaseMenu()
|
|||||||
optionsArray[options] = "New Freetext Msg";
|
optionsArray[options] = "New Freetext Msg";
|
||||||
optionsEnumArray[options++] = Freetext;
|
optionsEnumArray[options++] = Freetext;
|
||||||
}
|
}
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
optionsArray[options] = "Trace Route";
|
optionsArray[options] = "Trace Route";
|
||||||
optionsEnumArray[options++] = TraceRoute;
|
optionsEnumArray[options++] = TraceRoute;
|
||||||
|
#endif
|
||||||
optionsArray[options] = "Remove Favorite";
|
optionsArray[options] = "Remove Favorite";
|
||||||
optionsEnumArray[options++] = Remove;
|
optionsEnumArray[options++] = Remove;
|
||||||
|
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
bannerOptions.message = "Favorites";
|
||||||
|
#else
|
||||||
bannerOptions.message = "Favorites Action";
|
bannerOptions.message = "Favorites Action";
|
||||||
|
#endif
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||||
bannerOptions.optionsCount = options;
|
bannerOptions.optionsCount = options;
|
||||||
@@ -528,10 +576,12 @@ void menuHandler::positionBaseMenu()
|
|||||||
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
|
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
|
||||||
int options = 3;
|
int options = 3;
|
||||||
|
|
||||||
|
#if !MESHTASTIC_EXCLUDE_I2C
|
||||||
if (accelerometerThread) {
|
if (accelerometerThread) {
|
||||||
optionsArray[options] = "Compass Calibrate";
|
optionsArray[options] = "Compass Calibrate";
|
||||||
optionsEnumArray[options++] = CompassCalibrate;
|
optionsEnumArray[options++] = CompassCalibrate;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
bannerOptions.message = "Position Action";
|
bannerOptions.message = "Position Action";
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
@@ -544,8 +594,10 @@ void menuHandler::positionBaseMenu()
|
|||||||
} else if (selected == CompassMenu) {
|
} else if (selected == CompassMenu) {
|
||||||
menuQueue = compass_point_north_menu;
|
menuQueue = compass_point_north_menu;
|
||||||
screen->runNow();
|
screen->runNow();
|
||||||
|
#if !MESHTASTIC_EXCLUDE_I2C
|
||||||
} else if (selected == CompassCalibrate) {
|
} else if (selected == CompassCalibrate) {
|
||||||
accelerometerThread->calibrate(30);
|
accelerometerThread->calibrate(30);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
screen->showOverlayBanner(bannerOptions);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
@@ -554,11 +606,19 @@ void menuHandler::positionBaseMenu()
|
|||||||
void menuHandler::nodeListMenu()
|
void menuHandler::nodeListMenu()
|
||||||
{
|
{
|
||||||
enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd };
|
enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd };
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset Node"};
|
||||||
|
#else
|
||||||
static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"};
|
static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"};
|
||||||
|
#endif
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
bannerOptions.message = "Node Action";
|
bannerOptions.message = "Node Action";
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
bannerOptions.optionsCount = 3;
|
||||||
|
#else
|
||||||
bannerOptions.optionsCount = 5;
|
bannerOptions.optionsCount = 5;
|
||||||
|
#endif
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
if (selected == Favorite) {
|
if (selected == Favorite) {
|
||||||
menuQueue = add_favorite;
|
menuQueue = add_favorite;
|
||||||
@@ -665,7 +725,11 @@ void menuHandler::BluetoothToggleMenu()
|
|||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
|
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
bannerOptions.message = "Bluetooth";
|
||||||
|
#else
|
||||||
bannerOptions.message = "Toggle Bluetooth";
|
bannerOptions.message = "Toggle Bluetooth";
|
||||||
|
#endif
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 3;
|
bannerOptions.optionsCount = 3;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
@@ -725,7 +789,7 @@ void menuHandler::BrightnessPickerMenu()
|
|||||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
|
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
|
||||||
// For HELTEC devices, use analogWrite to control backlight
|
// For HELTEC devices, use analogWrite to control backlight
|
||||||
analogWrite(VTFT_LEDA, uiconfig.screen_brightness);
|
analogWrite(VTFT_LEDA, uiconfig.screen_brightness);
|
||||||
#elif defined(ST7789_CS)
|
#elif defined(ST7789_CS) || defined(ST7796_CS)
|
||||||
static_cast<TFTDisplay *>(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness);
|
static_cast<TFTDisplay *>(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness);
|
||||||
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
|
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
|
||||||
screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness);
|
screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness);
|
||||||
@@ -768,7 +832,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
|
|||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 10;
|
bannerOptions.optionsCount = 10;
|
||||||
bannerOptions.bannerCallback = [display](int selected) -> void {
|
bannerOptions.bannerCallback = [display](int selected) -> void {
|
||||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
|
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
|
||||||
uint8_t TFT_MESH_r = 0;
|
uint8_t TFT_MESH_r = 0;
|
||||||
uint8_t TFT_MESH_g = 0;
|
uint8_t TFT_MESH_g = 0;
|
||||||
uint8_t TFT_MESH_b = 0;
|
uint8_t TFT_MESH_b = 0;
|
||||||
@@ -857,7 +921,11 @@ void menuHandler::rebootMenu()
|
|||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Back", "Confirm"};
|
static const char *optionsArray[] = {"Back", "Confirm"};
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
bannerOptions.message = "Reboot";
|
||||||
|
#else
|
||||||
bannerOptions.message = "Reboot Device?";
|
bannerOptions.message = "Reboot Device?";
|
||||||
|
#endif
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 2;
|
bannerOptions.optionsCount = 2;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
@@ -877,7 +945,11 @@ void menuHandler::shutdownMenu()
|
|||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Back", "Confirm"};
|
static const char *optionsArray[] = {"Back", "Confirm"};
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
bannerOptions.message = "Shutdown";
|
||||||
|
#else
|
||||||
bannerOptions.message = "Shutdown Device?";
|
bannerOptions.message = "Shutdown Device?";
|
||||||
|
#endif
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 2;
|
bannerOptions.optionsCount = 2;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
@@ -894,7 +966,12 @@ void menuHandler::shutdownMenu()
|
|||||||
|
|
||||||
void menuHandler::addFavoriteMenu()
|
void menuHandler::addFavoriteMenu()
|
||||||
{
|
{
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
screen->showNodePicker("Node Favorite", 30000, [](uint32_t nodenum) -> void {
|
||||||
|
#else
|
||||||
screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
|
screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
|
||||||
|
|
||||||
|
#endif
|
||||||
LOG_WARN("Nodenum: %u", nodenum);
|
LOG_WARN("Nodenum: %u", nodenum);
|
||||||
nodeDB->set_favorite(true, nodenum);
|
nodeDB->set_favorite(true, nodenum);
|
||||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||||
@@ -1045,7 +1122,7 @@ void menuHandler::screenOptionsMenu()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only show screen color for TFT displays
|
// Only show screen color for TFT displays
|
||||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
|
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
|
||||||
optionsArray[options] = "Screen Color";
|
optionsArray[options] = "Screen Color";
|
||||||
optionsEnumArray[options++] = ScreenColor;
|
optionsEnumArray[options++] = ScreenColor;
|
||||||
#endif
|
#endif
|
||||||
@@ -1090,7 +1167,11 @@ void menuHandler::powerMenu()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
bannerOptions.message = "Power";
|
||||||
|
#else
|
||||||
bannerOptions.message = "Reboot / Shutdown";
|
bannerOptions.message = "Reboot / Shutdown";
|
||||||
|
#endif
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = options;
|
bannerOptions.optionsCount = options;
|
||||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ class menuHandler
|
|||||||
static void notificationsMenu();
|
static void notificationsMenu();
|
||||||
static void screenOptionsMenu();
|
static void screenOptionsMenu();
|
||||||
static void powerMenu();
|
static void powerMenu();
|
||||||
|
static void textMessageMenu();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void saveUIConfig();
|
static void saveUIConfig();
|
||||||
|
|||||||
@@ -181,12 +181,19 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
display->clear();
|
display->clear();
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
const int fixedTopHeight = 24;
|
||||||
|
const int windowX = 0;
|
||||||
|
const int windowY = fixedTopHeight;
|
||||||
|
const int windowWidth = 64;
|
||||||
|
const int windowHeight = SCREEN_HEIGHT - fixedTopHeight;
|
||||||
|
#else
|
||||||
const int navHeight = FONT_HEIGHT_SMALL;
|
const int navHeight = FONT_HEIGHT_SMALL;
|
||||||
const int scrollBottom = SCREEN_HEIGHT - navHeight;
|
const int scrollBottom = SCREEN_HEIGHT - navHeight;
|
||||||
const int usableHeight = scrollBottom;
|
const int usableHeight = scrollBottom;
|
||||||
const int textWidth = SCREEN_WIDTH;
|
const int textWidth = SCREEN_WIDTH;
|
||||||
|
|
||||||
|
#endif
|
||||||
bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
|
bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
|
||||||
bool isBold = config.display.heading_bold;
|
bool isBold = config.display.heading_bold;
|
||||||
|
|
||||||
@@ -201,7 +208,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||||
const char *messageString = "No messages";
|
const char *messageString = "No messages";
|
||||||
int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2);
|
int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2);
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
display->drawString(center_text, windowY + (windowHeight / 2) - (FONT_HEIGHT_SMALL / 2) - 5, messageString);
|
||||||
|
#else
|
||||||
display->drawString(center_text, getTextPositions(display)[2], messageString);
|
display->drawString(center_text, getTextPositions(display)[2], messageString);
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +220,10 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
|
||||||
char headerStr[80];
|
char headerStr[80];
|
||||||
const char *sender = "???";
|
const char *sender = "???";
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
if (node && node->has_user)
|
||||||
|
sender = node->user.short_name;
|
||||||
|
#else
|
||||||
if (node && node->has_user) {
|
if (node && node->has_user) {
|
||||||
if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) {
|
if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) {
|
||||||
sender = node->user.long_name;
|
sender = node->user.long_name;
|
||||||
@@ -216,6 +231,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
sender = node->user.short_name;
|
sender = node->user.short_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
|
uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
|
||||||
uint8_t timestampHours, timestampMinutes;
|
uint8_t timestampHours, timestampMinutes;
|
||||||
int32_t daysAgo;
|
int32_t daysAgo;
|
||||||
@@ -235,10 +251,61 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
sender);
|
sender);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
snprintf(headerStr, sizeof(headerStr), "%s from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
|
||||||
|
sender);
|
||||||
|
#else
|
||||||
snprintf(headerStr, sizeof(headerStr), "%s ago from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
|
snprintf(headerStr, sizeof(headerStr), "%s ago from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
|
||||||
sender);
|
sender);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||||
|
int headerY = getTextPositions(display)[1];
|
||||||
|
display->drawString(x, headerY, headerStr);
|
||||||
|
for (int separatorX = 0; separatorX < SCREEN_WIDTH; separatorX += 2) {
|
||||||
|
display->setPixel(separatorX, fixedTopHeight - 1);
|
||||||
|
}
|
||||||
|
cachedLines.clear();
|
||||||
|
std::string fullMsg(messageBuf);
|
||||||
|
std::string currentLine;
|
||||||
|
for (size_t i = 0; i < fullMsg.size();) {
|
||||||
|
unsigned char c = fullMsg[i];
|
||||||
|
size_t charLen = 1;
|
||||||
|
if ((c & 0xE0) == 0xC0)
|
||||||
|
charLen = 2;
|
||||||
|
else if ((c & 0xF0) == 0xE0)
|
||||||
|
charLen = 3;
|
||||||
|
else if ((c & 0xF8) == 0xF0)
|
||||||
|
charLen = 4;
|
||||||
|
std::string nextChar = fullMsg.substr(i, charLen);
|
||||||
|
std::string testLine = currentLine + nextChar;
|
||||||
|
if (display->getStringWidth(testLine.c_str()) > windowWidth) {
|
||||||
|
cachedLines.push_back(currentLine);
|
||||||
|
currentLine = nextChar;
|
||||||
|
} else {
|
||||||
|
currentLine = testLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += charLen;
|
||||||
|
}
|
||||||
|
if (!currentLine.empty())
|
||||||
|
cachedLines.push_back(currentLine);
|
||||||
|
cachedHeights = calculateLineHeights(cachedLines, emotes);
|
||||||
|
int yOffset = windowY;
|
||||||
|
int linesDrawn = 0;
|
||||||
|
for (size_t i = 0; i < cachedLines.size(); ++i) {
|
||||||
|
if (linesDrawn >= 2)
|
||||||
|
break;
|
||||||
|
int lineHeight = cachedHeights[i];
|
||||||
|
if (yOffset + lineHeight > windowY + windowHeight)
|
||||||
|
break;
|
||||||
|
drawStringWithEmotes(display, windowX, yOffset, cachedLines[i], emotes, numEmotes);
|
||||||
|
yOffset += lineHeight;
|
||||||
|
linesDrawn++;
|
||||||
|
}
|
||||||
|
screen->forceDisplay();
|
||||||
|
#else
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
#ifndef EXCLUDE_EMOJI
|
#ifndef EXCLUDE_EMOJI
|
||||||
// === Bounce animation setup ===
|
// === Bounce animation setup ===
|
||||||
@@ -355,6 +422,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
|
|
||||||
// Draw header at the end to sort out overlapping elements
|
// Draw header at the end to sort out overlapping elements
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)
|
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ extern bool haveGlyphs(const char *str);
|
|||||||
// Global screen instance
|
// Global screen instance
|
||||||
extern graphics::Screen *screen;
|
extern graphics::Screen *screen;
|
||||||
|
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
static uint32_t lastSwitchTime = 0;
|
||||||
|
#else
|
||||||
|
#endif
|
||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
namespace NodeListRenderer
|
namespace NodeListRenderer
|
||||||
@@ -393,9 +397,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
{
|
{
|
||||||
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
|
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
|
||||||
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
|
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
int columnWidth = display->getWidth();
|
||||||
|
#else
|
||||||
int columnWidth = display->getWidth() / 2;
|
int columnWidth = display->getWidth() / 2;
|
||||||
|
#endif
|
||||||
display->clear();
|
display->clear();
|
||||||
|
|
||||||
// Draw the battery/time header
|
// Draw the battery/time header
|
||||||
@@ -408,8 +414,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
|
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
|
||||||
|
|
||||||
int visibleNodeRows = totalRowsAvailable;
|
int visibleNodeRows = totalRowsAvailable;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
int totalColumns = 1;
|
||||||
|
#else
|
||||||
int totalColumns = 2;
|
int totalColumns = 2;
|
||||||
|
#endif
|
||||||
int startIndex = scrollIndex * visibleNodeRows * totalColumns;
|
int startIndex = scrollIndex * visibleNodeRows * totalColumns;
|
||||||
if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) {
|
if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) {
|
||||||
startIndex++; // skip own node
|
startIndex++; // skip own node
|
||||||
@@ -445,12 +454,14 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
// Draw column separator
|
// Draw column separator
|
||||||
if (shownCount > 0) {
|
if (shownCount > 0) {
|
||||||
const int firstNodeY = y + 3;
|
const int firstNodeY = y + 3;
|
||||||
drawColumnSeparator(display, x, firstNodeY, lastNodeY);
|
drawColumnSeparator(display, x, firstNodeY, lastNodeY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
const int scrollStartY = y + 3;
|
const int scrollStartY = y + 3;
|
||||||
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
||||||
}
|
}
|
||||||
@@ -468,6 +479,13 @@ void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state,
|
|||||||
|
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
|
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
display->clear();
|
||||||
|
if (now - lastSwitchTime >= 3000) {
|
||||||
|
display->display();
|
||||||
|
lastSwitchTime = now;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
// On very first call (on boot or state enter)
|
// On very first call (on boot or state enter)
|
||||||
if (lastRenderedMode == MODE_COUNT) {
|
if (lastRenderedMode == MODE_COUNT) {
|
||||||
currentMode = MODE_LAST_HEARD;
|
currentMode = MODE_LAST_HEARD;
|
||||||
@@ -522,6 +540,14 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
|
|||||||
double lat = DegD(ourNode->position.latitude_i);
|
double lat = DegD(ourNode->position.latitude_i);
|
||||||
double lon = DegD(ourNode->position.longitude_i);
|
double lon = DegD(ourNode->position.longitude_i);
|
||||||
|
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
display->clear();
|
||||||
|
uint32_t now = millis();
|
||||||
|
if (now - lastSwitchTime >= 2000) {
|
||||||
|
display->display();
|
||||||
|
lastSwitchTime = now;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
|
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
|
||||||
#if HAS_GPS
|
#if HAS_GPS
|
||||||
if (screen->hasHeading()) {
|
if (screen->hasHeading()) {
|
||||||
|
|||||||
@@ -459,6 +459,135 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
// count lines
|
// count lines
|
||||||
|
|
||||||
uint16_t boxWidth = hPadding * 2 + maxWidth;
|
uint16_t boxWidth = hPadding * 2 + maxWidth;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
if (needs_bell) {
|
||||||
|
if (isHighResolution && boxWidth <= 150)
|
||||||
|
boxWidth += 26;
|
||||||
|
if (!isHighResolution && boxWidth <= 100)
|
||||||
|
boxWidth += 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t screenHeight = display->height();
|
||||||
|
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
|
||||||
|
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
|
||||||
|
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
|
||||||
|
uint16_t boxHeight = contentHeight + vPadding * 2;
|
||||||
|
if (visibleTotalLines == 1)
|
||||||
|
boxHeight += (isHighResolution ? 4 : 3);
|
||||||
|
|
||||||
|
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
|
||||||
|
if (totalLines > visibleTotalLines)
|
||||||
|
boxWidth += (isHighResolution ? 4 : 2);
|
||||||
|
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
|
||||||
|
|
||||||
|
if (visibleTotalLines == 1) {
|
||||||
|
boxTop += 25;
|
||||||
|
}
|
||||||
|
if (alertBannerOptions < 3) {
|
||||||
|
int missingLines = 3 - alertBannerOptions;
|
||||||
|
int moveUp = missingLines * (effectiveLineHeight / 2);
|
||||||
|
boxTop -= moveUp;
|
||||||
|
if (boxTop < 0)
|
||||||
|
boxTop = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Draw Box ===
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->fillRect(boxLeft, boxTop, boxWidth, boxHeight);
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
|
||||||
|
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
|
||||||
|
display->fillRect(boxLeft - 2, boxTop, 1, boxHeight);
|
||||||
|
display->fillRect(boxLeft + boxWidth + 1, boxTop, 1, boxHeight);
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->fillRect(boxLeft, boxTop, 1, 1);
|
||||||
|
display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1);
|
||||||
|
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
|
||||||
|
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
|
||||||
|
display->setColor(WHITE);
|
||||||
|
int16_t lineY = boxTop + vPadding;
|
||||||
|
int swingRange = 8;
|
||||||
|
static int swingOffset = 0;
|
||||||
|
static bool swingRight = true;
|
||||||
|
static unsigned long lastSwingTime = 0;
|
||||||
|
unsigned long now = millis();
|
||||||
|
int swingSpeedMs = 10 / (swingRange * 2);
|
||||||
|
if (now - lastSwingTime >= (unsigned long)swingSpeedMs) {
|
||||||
|
lastSwingTime = now;
|
||||||
|
if (swingRight) {
|
||||||
|
swingOffset++;
|
||||||
|
if (swingOffset >= swingRange)
|
||||||
|
swingRight = false;
|
||||||
|
} else {
|
||||||
|
swingOffset--;
|
||||||
|
if (swingOffset <= 0)
|
||||||
|
swingRight = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < lineCount; i++) {
|
||||||
|
bool isTitle = (i == 0);
|
||||||
|
int globalOptionIndex = (i - 1) + firstOptionToShow;
|
||||||
|
bool isSelectedOption = (!isTitle && globalOptionIndex >= 0 && globalOptionIndex == curSelected);
|
||||||
|
|
||||||
|
uint16_t visibleWidth = 64 - hPadding * 2;
|
||||||
|
if (totalLines > visibleTotalLines)
|
||||||
|
visibleWidth -= 6;
|
||||||
|
char lineBuffer[lineLengths[i] + 1];
|
||||||
|
strncpy(lineBuffer, lines[i], lineLengths[i]);
|
||||||
|
lineBuffer[lineLengths[i]] = '\0';
|
||||||
|
|
||||||
|
if (isTitle) {
|
||||||
|
if (visibleTotalLines == 1) {
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
|
||||||
|
} else {
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
|
||||||
|
display->setColor(WHITE);
|
||||||
|
if (needs_bell) {
|
||||||
|
int bellY = boxTop + (FONT_HEIGHT_SMALL - 8) / 2;
|
||||||
|
display->drawXbm(boxLeft + (boxWidth - lineWidths[i]) / 2 - 10, bellY, 8, 8, bell_alert);
|
||||||
|
display->drawXbm(boxLeft + (boxWidth + lineWidths[i]) / 2 + 2, bellY, 8, 8, bell_alert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lineY = boxTop + effectiveLineHeight + 1;
|
||||||
|
} else if (isSelectedOption) {
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
|
||||||
|
display->setColor(BLACK);
|
||||||
|
if (lineLengths[i] > 15 && lineWidths[i] > visibleWidth) {
|
||||||
|
int textX = boxLeft + hPadding + swingOffset;
|
||||||
|
display->drawString(textX, lineY - 1, lineBuffer);
|
||||||
|
} else {
|
||||||
|
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY - 1, lineBuffer);
|
||||||
|
}
|
||||||
|
display->setColor(WHITE);
|
||||||
|
lineY += effectiveLineHeight;
|
||||||
|
} else {
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY, lineBuffer);
|
||||||
|
lineY += effectiveLineHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (totalLines > visibleTotalLines) {
|
||||||
|
const uint8_t scrollBarWidth = 5;
|
||||||
|
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
|
||||||
|
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
|
||||||
|
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
|
||||||
|
float ratio = (float)visibleTotalLines / totalLines;
|
||||||
|
uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4);
|
||||||
|
float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines);
|
||||||
|
uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight);
|
||||||
|
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
|
||||||
|
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (needs_bell) {
|
if (needs_bell) {
|
||||||
if (isHighResolution && boxWidth <= 150)
|
if (isHighResolution && boxWidth <= 150)
|
||||||
boxWidth += 26;
|
boxWidth += 26;
|
||||||
@@ -547,6 +676,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
|
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
|
||||||
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
|
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the last text message we received
|
/// Draw the last text message we received
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
// External variables
|
// External variables
|
||||||
extern graphics::Screen *screen;
|
extern graphics::Screen *screen;
|
||||||
|
static uint32_t lastSwitchTime = 0;
|
||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
|
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
|
||||||
@@ -194,7 +194,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \
|
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
|
|
||||||
if (isHighResolution) {
|
if (isHighResolution) {
|
||||||
@@ -218,7 +218,6 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
|
|||||||
// **********************
|
// **********************
|
||||||
void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (favoritedNodes.empty())
|
if (favoritedNodes.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -230,8 +229,15 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
|
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
|
||||||
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
|
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
|
||||||
return;
|
return;
|
||||||
|
uint32_t now = millis();
|
||||||
display->clear();
|
display->clear();
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
|
||||||
|
{
|
||||||
|
display->display();
|
||||||
|
lastSwitchTime = now;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
currentFavoriteNodeNum = node->num;
|
currentFavoriteNodeNum = node->num;
|
||||||
// === Create the shortName and title string ===
|
// === Create the shortName and title string ===
|
||||||
const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node";
|
const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node";
|
||||||
@@ -250,9 +256,13 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
// List of available macro Y positions in order, from top to bottom.
|
// List of available macro Y positions in order, from top to bottom.
|
||||||
int line = 1; // which slot to use next
|
int line = 1; // which slot to use next
|
||||||
std::string usernameStr;
|
std::string usernameStr;
|
||||||
|
|
||||||
// === 1. Long Name (always try to show first) ===
|
// === 1. Long Name (always try to show first) ===
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr;
|
||||||
|
#else
|
||||||
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
|
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (username) {
|
if (username) {
|
||||||
usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case
|
usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case
|
||||||
// Print node's long name (e.g. "Backpack Node")
|
// Print node's long name (e.g. "Backpack Node")
|
||||||
@@ -307,7 +317,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
if (seenStr[0] && line < 5) {
|
if (seenStr[0] && line < 5) {
|
||||||
display->drawString(x, getTextPositions(display)[line++], seenStr);
|
display->drawString(x, getTextPositions(display)[line++], seenStr);
|
||||||
}
|
}
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
// === 4. Uptime (only show if metric is present) ===
|
// === 4. Uptime (only show if metric is present) ===
|
||||||
char uptimeStr[32] = "";
|
char uptimeStr[32] = "";
|
||||||
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
|
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
|
||||||
@@ -479,6 +489,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
}
|
}
|
||||||
// else show nothing
|
// else show nothing
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************
|
// ****************************
|
||||||
@@ -492,7 +503,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
int line = 1;
|
int line = 1;
|
||||||
|
|
||||||
// === Header ===
|
// === Header ===
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
graphics::drawCommonHeader(display, x, y, "Home");
|
||||||
|
#else
|
||||||
graphics::drawCommonHeader(display, x, y, "");
|
graphics::drawCommonHeader(display, x, y, "");
|
||||||
|
#endif
|
||||||
|
|
||||||
// === Content below header ===
|
// === Content below header ===
|
||||||
|
|
||||||
@@ -507,20 +522,25 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
config.display.heading_bold = false;
|
config.display.heading_bold = false;
|
||||||
|
|
||||||
// Display Region and Channel Utilization
|
// Display Region and Channel Utilization
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
||||||
|
#else
|
||||||
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
||||||
|
#endif
|
||||||
char uptimeStr[32] = "";
|
char uptimeStr[32] = "";
|
||||||
uint32_t uptime = millis() / 1000;
|
uint32_t uptime = millis() / 1000;
|
||||||
uint32_t days = uptime / 86400;
|
uint32_t days = uptime / 86400;
|
||||||
uint32_t hours = (uptime % 86400) / 3600;
|
uint32_t hours = (uptime % 86400) / 3600;
|
||||||
uint32_t mins = (uptime % 3600) / 60;
|
uint32_t mins = (uptime % 3600) / 60;
|
||||||
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
|
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
if (days)
|
if (days)
|
||||||
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
|
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
|
||||||
else if (hours)
|
else if (hours)
|
||||||
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
|
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
|
||||||
else
|
else
|
||||||
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
|
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
|
||||||
|
#endif
|
||||||
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
|
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
|
||||||
|
|
||||||
// === Second Row: Satellites and Voltage ===
|
// === Second Row: Satellites and Voltage ===
|
||||||
@@ -549,6 +569,21 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
line += 1;
|
||||||
|
|
||||||
|
// === Node Identity ===
|
||||||
|
int textWidth = 0;
|
||||||
|
int nameX = 0;
|
||||||
|
char shortnameble[35];
|
||||||
|
snprintf(shortnameble, sizeof(shortnameble), "%s",
|
||||||
|
graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : "");
|
||||||
|
|
||||||
|
// === ShortName Centered ===
|
||||||
|
textWidth = display->getStringWidth(shortnameble);
|
||||||
|
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
|
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
||||||
|
#else
|
||||||
if (powerStatus->getHasBattery()) {
|
if (powerStatus->getHasBattery()) {
|
||||||
char batStr[20];
|
char batStr[20];
|
||||||
int batV = powerStatus->getBatteryVoltageMv() / 1000;
|
int batV = powerStatus->getBatteryVoltageMv() / 1000;
|
||||||
@@ -674,6 +709,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start Functions to write date/time to the screen
|
// Start Functions to write date/time to the screen
|
||||||
@@ -832,6 +868,28 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
|
|||||||
// needs to be drawn relative to x and y
|
// needs to be drawn relative to x and y
|
||||||
|
|
||||||
// draw centered icon left to right and centered above the one line of app text
|
// draw centered icon left to right and centered above the one line of app text
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
|
||||||
|
display->setFont(FONT_MEDIUM);
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
// Draw region in upper left
|
||||||
|
if (upperMsg) {
|
||||||
|
int msgWidth = display->getStringWidth(upperMsg);
|
||||||
|
int msgX = x + (SCREEN_WIDTH - msgWidth) / 2;
|
||||||
|
int msgY = y;
|
||||||
|
display->drawString(msgX, msgY, upperMsg);
|
||||||
|
}
|
||||||
|
// Draw version and short name in bottom middle
|
||||||
|
char buf[25];
|
||||||
|
snprintf(buf, sizeof(buf), "%s %s", xstr(APP_VERSION_SHORT),
|
||||||
|
graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : "");
|
||||||
|
|
||||||
|
display->drawString(x + getStringCenteredX(buf), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, buf);
|
||||||
|
screen->forceDisplay();
|
||||||
|
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
|
||||||
|
#else
|
||||||
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
|
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
|
||||||
icon_width, icon_height, icon_bits);
|
icon_width, icon_height, icon_bits);
|
||||||
|
|
||||||
@@ -840,7 +898,6 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
|
|||||||
const char *title = "meshtastic.org";
|
const char *title = "meshtastic.org";
|
||||||
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
// Draw region in upper left
|
// Draw region in upper left
|
||||||
if (upperMsg)
|
if (upperMsg)
|
||||||
display->drawString(x + 0, y + 0, upperMsg);
|
display->drawString(x + 0, y + 0, upperMsg);
|
||||||
@@ -855,6 +912,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
|
|||||||
screen->forceDisplay();
|
screen->forceDisplay();
|
||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
|
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************
|
// ****************************
|
||||||
@@ -930,15 +988,26 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime);
|
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime);
|
||||||
char fullLine[40];
|
char fullLine[40];
|
||||||
snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
|
snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
display->drawString(0, getTextPositions(display)[line++], fullLine);
|
display->drawString(0, getTextPositions(display)[line++], fullLine);
|
||||||
|
#endif
|
||||||
|
|
||||||
// === Third Row: Latitude ===
|
// === Third Row: Latitude ===
|
||||||
char latStr[32];
|
char latStr[32];
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
snprintf(latStr, sizeof(latStr), "Lat:%.5f", geoCoord.getLatitude() * 1e-7);
|
||||||
|
display->drawString(x, getTextPositions(display)[line++] + 2, latStr);
|
||||||
|
#else
|
||||||
snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
|
snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
|
||||||
display->drawString(x, getTextPositions(display)[line++], latStr);
|
display->drawString(x, getTextPositions(display)[line++], latStr);
|
||||||
|
#endif
|
||||||
|
|
||||||
// === Fourth Row: Longitude ===
|
// === Fourth Row: Longitude ===
|
||||||
char lonStr[32];
|
char lonStr[32];
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
snprintf(lonStr, sizeof(lonStr), "Lon:%.3f", geoCoord.getLongitude() * 1e-7);
|
||||||
|
display->drawString(x, getTextPositions(display)[line++] + 4, lonStr);
|
||||||
|
#else
|
||||||
snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7);
|
snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7);
|
||||||
display->drawString(x, getTextPositions(display)[line++], lonStr);
|
display->drawString(x, getTextPositions(display)[line++], lonStr);
|
||||||
|
|
||||||
@@ -950,8 +1019,9 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude());
|
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude());
|
||||||
}
|
}
|
||||||
display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
|
display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
// === Draw Compass if heading is valid ===
|
// === Draw Compass if heading is valid ===
|
||||||
if (validHeading) {
|
if (validHeading) {
|
||||||
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
|
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
|
||||||
@@ -1034,6 +1104,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USERPREFS_OEM_TEXT
|
#ifdef USERPREFS_OEM_TEXT
|
||||||
@@ -1190,7 +1261,6 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Knock the corners off the square
|
// Knock the corners off the square
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
display->drawRect(rectX, y - 2, 1, 1);
|
display->drawRect(rectX, y - 2, 1, 1);
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03
|
|||||||
0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
|
0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
|
||||||
|
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \
|
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \
|
||||||
|
ARCH_PORTDUINO) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
|
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
|
||||||
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
|
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
|
||||||
@@ -286,6 +287,9 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
|
|||||||
#define analog_icon_clock_height 8
|
#define analog_icon_clock_height 8
|
||||||
const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
|
const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
|
||||||
0b00100100, 0b01000010, 0b01000010, 0b11111111};
|
0b00100100, 0b01000010, 0b01000010, 0b11111111};
|
||||||
|
#ifdef M5STACK_UNITC6L
|
||||||
|
#include "img/icon_small.xbm"
|
||||||
|
#else
|
||||||
#include "img/icon.xbm"
|
#include "img/icon.xbm"
|
||||||
|
#endif
|
||||||
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
|
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
|
||||||
30
src/graphics/img/icon_small.xbm
Normal file
30
src/graphics/img/icon_small.xbm
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef USERPREFS_HAS_SPLASH
|
||||||
|
#define icon_width 50
|
||||||
|
#define icon_height 20
|
||||||
|
static uint8_t icon_bits[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x80, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x80,
|
||||||
|
0x07, 0xc0, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x0f,
|
||||||
|
0xc0, 0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xe0,
|
||||||
|
0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x1f,
|
||||||
|
0x00, 0x00, 0x00, 0xf0, 0x03, 0xf0, 0x3f, 0x00,
|
||||||
|
0x00, 0x00, 0xf8, 0x03, 0xf8, 0x7f, 0x00, 0x00,
|
||||||
|
0x00, 0xf8, 0x01, 0xfc, 0x7e, 0x00, 0x00, 0x00,
|
||||||
|
0xfc, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0xfe,
|
||||||
|
0x00, 0x7e, 0xf8, 0x00, 0x00, 0x00, 0x7e, 0x00,
|
||||||
|
0x3f, 0xf8, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x1f,
|
||||||
|
0xf0, 0x01, 0x00, 0x00, 0x1f, 0x80, 0x1f, 0xe0,
|
||||||
|
0x03, 0x00, 0x80, 0x1f, 0xc0, 0x0f, 0xe0, 0x03,
|
||||||
|
0x00, 0x80, 0x0f, 0xc0, 0x07, 0xc0, 0x07, 0x00,
|
||||||
|
0xc0, 0x0f, 0xe0, 0x07, 0x80, 0x0f, 0x00, 0xe0,
|
||||||
|
0x07, 0xf0, 0x03, 0x80, 0x1f, 0x00, 0xe0, 0x03,
|
||||||
|
0xf8, 0x03, 0x00, 0x1f, 0x00, 0xf0, 0x03, 0xf8,
|
||||||
|
0x01, 0x00, 0x3f, 0x00, 0xf8, 0x01, 0xfc, 0x00,
|
||||||
|
0x00, 0x7e, 0x00, 0xfc, 0x00, 0xfe, 0x00, 0x00,
|
||||||
|
0x7e, 0x00, 0xfc, 0x00, 0x7e, 0x00, 0x00, 0xfc,
|
||||||
|
0x00, 0x7e, 0x00, 0x3f, 0x00, 0x00, 0xf8, 0x00,
|
||||||
|
0x7e, 0x00, 0x3e, 0x00, 0x00, 0xf8, 0x00, 0x38,
|
||||||
|
0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00 };
|
||||||
|
#endif
|
||||||
68
src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp
Normal file
68
src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#include "./ZJY122250_0213BAAMFGN.h"
|
||||||
|
|
||||||
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
|
|
||||||
|
using namespace NicheGraphics::Drivers;
|
||||||
|
|
||||||
|
// Map the display controller IC's output to the connected panel
|
||||||
|
void ZJY122250_0213BAAMFGN::configScanning()
|
||||||
|
{
|
||||||
|
// "Driver output control"
|
||||||
|
// Scan gates from 0 to 249 (vertical resolution 250px)
|
||||||
|
sendCommand(0x01);
|
||||||
|
sendData(0xF9);
|
||||||
|
sendData(0x00);
|
||||||
|
sendData(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify which information is used to control the sequence of voltages applied to move the pixels
|
||||||
|
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
|
||||||
|
// the controller IC's OTP memory, when the update procedure begins.
|
||||||
|
void ZJY122250_0213BAAMFGN::configWaveform()
|
||||||
|
{
|
||||||
|
switch (updateType) {
|
||||||
|
case FAST:
|
||||||
|
sendCommand(0x3C); // Border waveform:
|
||||||
|
sendData(0x80); // VCOM
|
||||||
|
break;
|
||||||
|
case FULL:
|
||||||
|
default:
|
||||||
|
sendCommand(0x3C); // Border waveform:
|
||||||
|
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendCommand(0x18); // Temperature sensor:
|
||||||
|
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZJY122250_0213BAAMFGN::configUpdateSequence()
|
||||||
|
{
|
||||||
|
switch (updateType) {
|
||||||
|
case FAST:
|
||||||
|
sendCommand(0x22); // Set "update sequence"
|
||||||
|
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FULL:
|
||||||
|
default:
|
||||||
|
sendCommand(0x22); // Set "update sequence"
|
||||||
|
sendData(0xF7); // Will load LUT from OTP memory
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the refresh operation has been started,
|
||||||
|
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
|
||||||
|
// Only used when refresh is "async"
|
||||||
|
void ZJY122250_0213BAAMFGN::detachFromUpdate()
|
||||||
|
{
|
||||||
|
switch (updateType) {
|
||||||
|
case FAST:
|
||||||
|
return beginPolling(50, 500); // At least 500ms for fast refresh
|
||||||
|
case FULL:
|
||||||
|
default:
|
||||||
|
return beginPolling(100, 2000); // At least 2 seconds for full refresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
42
src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h
Normal file
42
src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
E-Ink display driver
|
||||||
|
- ZJY122250_0213BAAMFGN
|
||||||
|
- Manufacturer: Zhongjingyuan
|
||||||
|
- Size: 2.13 inch
|
||||||
|
- Resolution: 250px x 122px
|
||||||
|
- Flex connector marking (not a unique identifier): FPC-A002
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#include "./SSD16XX.h"
|
||||||
|
|
||||||
|
namespace NicheGraphics::Drivers
|
||||||
|
{
|
||||||
|
class ZJY122250_0213BAAMFGN : public SSD16XX
|
||||||
|
{
|
||||||
|
// Display properties
|
||||||
|
private:
|
||||||
|
static constexpr uint32_t width = 122;
|
||||||
|
static constexpr uint32_t height = 250;
|
||||||
|
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ZJY122250_0213BAAMFGN() : SSD16XX(width, height, supported) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void configScanning() override;
|
||||||
|
virtual void configWaveform() override;
|
||||||
|
virtual void configUpdateSequence() override;
|
||||||
|
void detachFromUpdate() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace NicheGraphics::Drivers
|
||||||
|
|
||||||
|
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
@@ -7,12 +7,7 @@ using namespace NicheGraphics;
|
|||||||
|
|
||||||
// Timing for "maintenance"
|
// Timing for "maintenance"
|
||||||
// Paying off full-refresh debt with unprovoked updates, if the display is not very active
|
// Paying off full-refresh debt with unprovoked updates, if the display is not very active
|
||||||
|
|
||||||
#ifdef SEEED_WIO_TRACKER_L1
|
|
||||||
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 5 * 1000UL;
|
|
||||||
#else
|
|
||||||
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL;
|
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL;
|
||||||
#endif
|
|
||||||
static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL;
|
static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL;
|
||||||
|
|
||||||
InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator")
|
InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator")
|
||||||
|
|||||||
73
src/input/RotaryEncoderImpl.cpp
Normal file
73
src/input/RotaryEncoderImpl.cpp
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#ifdef T_LORA_PAGER
|
||||||
|
|
||||||
|
#include "RotaryEncoderImpl.h"
|
||||||
|
#include "InputBroker.h"
|
||||||
|
#include "RotaryEncoder.h"
|
||||||
|
|
||||||
|
#define ORIGIN_NAME "RotaryEncoder"
|
||||||
|
|
||||||
|
RotaryEncoderImpl *rotaryEncoderImpl;
|
||||||
|
|
||||||
|
RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME)
|
||||||
|
{
|
||||||
|
rotary = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
|
||||||
|
eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
|
||||||
|
eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
|
||||||
|
|
||||||
|
rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b,
|
||||||
|
moduleConfig.canned_message.inputbroker_pin_press);
|
||||||
|
rotary->resetButton();
|
||||||
|
|
||||||
|
inputBroker->registerSource(this);
|
||||||
|
|
||||||
|
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
|
||||||
|
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
|
||||||
|
eventPressed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t RotaryEncoderImpl::runOnce()
|
||||||
|
{
|
||||||
|
InputEvent e{originName, INPUT_BROKER_NONE, 0, 0, 0};
|
||||||
|
static uint32_t lastPressed = millis();
|
||||||
|
if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
|
||||||
|
if (lastPressed + 200 < millis()) {
|
||||||
|
LOG_DEBUG("Rotary event Press");
|
||||||
|
lastPressed = millis();
|
||||||
|
e.inputEvent = this->eventPressed;
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||||
|
this->notifyObservers(&e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
28
src/input/RotaryEncoderImpl.h
Normal file
28
src/input/RotaryEncoderImpl.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
|
||||||
|
|
||||||
|
#include "InputBroker.h"
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
|
#include "mesh/NodeDB.h"
|
||||||
|
|
||||||
|
class RotaryEncoder;
|
||||||
|
|
||||||
|
class RotaryEncoderImpl : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RotaryEncoderImpl();
|
||||||
|
bool init(void);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
|
input_broker_event eventCw = INPUT_BROKER_NONE;
|
||||||
|
input_broker_event eventCcw = INPUT_BROKER_NONE;
|
||||||
|
input_broker_event eventPressed = INPUT_BROKER_NONE;
|
||||||
|
|
||||||
|
RotaryEncoder *rotary;
|
||||||
|
const char *originName;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern RotaryEncoderImpl *rotaryEncoderImpl;
|
||||||
@@ -18,14 +18,23 @@ void RotaryEncoderInterruptBase::init(
|
|||||||
this->_eventCcw = eventCcw;
|
this->_eventCcw = eventCcw;
|
||||||
this->_eventPressed = eventPressed;
|
this->_eventPressed = eventPressed;
|
||||||
|
|
||||||
pinMode(pinPress, INPUT_PULLUP);
|
bool isRAK = false;
|
||||||
pinMode(this->_pinA, INPUT_PULLUP);
|
#ifdef RAK_4631
|
||||||
pinMode(this->_pinB, INPUT_PULLUP);
|
isRAK = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
// attachInterrupt(pinPress, onIntPress, RISING);
|
if (!isRAK || pinPress != 0) {
|
||||||
attachInterrupt(pinPress, onIntPress, RISING);
|
pinMode(pinPress, INPUT_PULLUP);
|
||||||
attachInterrupt(this->_pinA, onIntA, CHANGE);
|
attachInterrupt(pinPress, onIntPress, RISING);
|
||||||
attachInterrupt(this->_pinB, onIntB, CHANGE);
|
}
|
||||||
|
if (!isRAK || this->_pinA != 0) {
|
||||||
|
pinMode(this->_pinA, INPUT_PULLUP);
|
||||||
|
attachInterrupt(this->_pinA, onIntA, CHANGE);
|
||||||
|
}
|
||||||
|
if (!isRAK || this->_pinA != 0) {
|
||||||
|
pinMode(this->_pinB, INPUT_PULLUP);
|
||||||
|
attachInterrupt(this->_pinB, onIntB, CHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
this->rotaryLevelA = digitalRead(this->_pinA);
|
this->rotaryLevelA = digitalRead(this->_pinA);
|
||||||
this->rotaryLevelB = digitalRead(this->_pinB);
|
this->rotaryLevelB = digitalRead(this->_pinB);
|
||||||
|
|||||||
230
src/input/TLoraPagerKeyboard.cpp
Normal file
230
src/input/TLoraPagerKeyboard.cpp
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#if defined(T_LORA_PAGER)
|
||||||
|
|
||||||
|
#include "TLoraPagerKeyboard.h"
|
||||||
|
#include "main.h"
|
||||||
|
|
||||||
|
#ifndef LEDC_BACKLIGHT_CHANNEL
|
||||||
|
#define LEDC_BACKLIGHT_CHANNEL 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LEDC_BACKLIGHT_BIT_WIDTH
|
||||||
|
#define LEDC_BACKLIGHT_BIT_WIDTH 8
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LEDC_BACKLIGHT_FREQ
|
||||||
|
#define LEDC_BACKLIGHT_FREQ 1000 // Hz
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define _TCA8418_COLS 10
|
||||||
|
#define _TCA8418_ROWS 4
|
||||||
|
#define _TCA8418_NUM_KEYS 31
|
||||||
|
|
||||||
|
#define _TCA8418_MULTI_TAP_THRESHOLD 1500
|
||||||
|
|
||||||
|
using Key = TCA8418KeyboardBase::TCA8418Key;
|
||||||
|
|
||||||
|
constexpr uint8_t modifierRightShiftKey = 29 - 1; // keynum -1
|
||||||
|
constexpr uint8_t modifierRightShift = 0b0001;
|
||||||
|
constexpr uint8_t modifierSymKey = 21 - 1;
|
||||||
|
constexpr uint8_t modifierSym = 0b0010;
|
||||||
|
|
||||||
|
// Num chars per key, Modulus for rotating through characters
|
||||||
|
static uint8_t TLoraPagerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||||
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
|
||||||
|
|
||||||
|
static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'},
|
||||||
|
{'w', 'W', '2'},
|
||||||
|
{'e', 'E', '3'},
|
||||||
|
{'r', 'R', '4'},
|
||||||
|
{'t', 'T', '5'},
|
||||||
|
{'y', 'Y', '6'},
|
||||||
|
{'u', 'U', '7'},
|
||||||
|
{'i', 'I', '8'},
|
||||||
|
{'o', 'O', '9'},
|
||||||
|
{'p', 'P', '0'},
|
||||||
|
{'a', 'A', '*'},
|
||||||
|
{'s', 'S', '/'},
|
||||||
|
{'d', 'D', '+'},
|
||||||
|
{'f', 'F', '-'},
|
||||||
|
{'g', 'G', '='},
|
||||||
|
{'h', 'H', ':'},
|
||||||
|
{'j', 'J', '\''},
|
||||||
|
{'k', 'K', '"'},
|
||||||
|
{'l', 'L', '@'},
|
||||||
|
{Key::SELECT, 0x00, Key::TAB},
|
||||||
|
{0x00, 0x00, 0x00},
|
||||||
|
{'z', 'Z', '_'},
|
||||||
|
{'x', 'X', '$'},
|
||||||
|
{'c', 'C', ';'},
|
||||||
|
{'v', 'V', '?'},
|
||||||
|
{'b', 'B', '!'},
|
||||||
|
{'n', 'N', ','},
|
||||||
|
{'m', 'M', '.'},
|
||||||
|
{0x00, 0x00, 0x00},
|
||||||
|
{Key::BSP, 0x00, Key::ESC},
|
||||||
|
{' ', 0x00, Key::BL_TOGGLE}};
|
||||||
|
|
||||||
|
TLoraPagerKeyboard::TLoraPagerKeyboard()
|
||||||
|
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
|
||||||
|
last_tap(0L), char_idx(0), tap_interval(0)
|
||||||
|
{
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||||
|
ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
|
||||||
|
#else
|
||||||
|
ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
|
||||||
|
ledcAttachPin(KB_BL_PIN, LEDC_BACKLIGHT_CHANNEL);
|
||||||
|
#endif
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLoraPagerKeyboard::reset(void)
|
||||||
|
{
|
||||||
|
TCA8418KeyboardBase::reset();
|
||||||
|
pinMode(KB_BL_PIN, OUTPUT);
|
||||||
|
digitalWrite(KB_BL_PIN, LOW);
|
||||||
|
setBacklight(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle multi-key presses (shift and alt)
|
||||||
|
void TLoraPagerKeyboard::trigger()
|
||||||
|
{
|
||||||
|
uint8_t count = keyCount();
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
for (uint8_t i = 0; i < count; ++i) {
|
||||||
|
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
|
||||||
|
uint8_t key = k & 0x7F;
|
||||||
|
if (k & 0x80) {
|
||||||
|
pressed(key);
|
||||||
|
} else {
|
||||||
|
released();
|
||||||
|
state = Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLoraPagerKeyboard::setBacklight(bool on)
|
||||||
|
{
|
||||||
|
toggleBacklight(!on);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLoraPagerKeyboard::pressed(uint8_t key)
|
||||||
|
{
|
||||||
|
if (state == Init || state == Busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED ||
|
||||||
|
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY) {
|
||||||
|
hapticFeedback();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||||
|
modifierFlag = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t next_key = 0;
|
||||||
|
int row = (key - 1) / 10;
|
||||||
|
int col = (key - 1) % 10;
|
||||||
|
|
||||||
|
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||||
|
return; // Invalid key
|
||||||
|
}
|
||||||
|
|
||||||
|
next_key = row * _TCA8418_COLS + col;
|
||||||
|
state = Held;
|
||||||
|
|
||||||
|
uint32_t now = millis();
|
||||||
|
tap_interval = now - last_tap;
|
||||||
|
|
||||||
|
updateModifierFlag(next_key);
|
||||||
|
if (isModifierKey(next_key)) {
|
||||||
|
last_modifier_time = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tap_interval < 0) {
|
||||||
|
last_tap = 0;
|
||||||
|
state = Busy;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||||
|
char_idx = 0;
|
||||||
|
} else {
|
||||||
|
char_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_key = next_key;
|
||||||
|
last_tap = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLoraPagerKeyboard::released()
|
||||||
|
{
|
||||||
|
if (state != Held) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||||
|
last_key = -1;
|
||||||
|
state = Idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t now = millis();
|
||||||
|
last_tap = now;
|
||||||
|
|
||||||
|
if (TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]] == Key::BL_TOGGLE) {
|
||||||
|
toggleBacklight();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
queueEvent(TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]]);
|
||||||
|
if (isModifierKey(last_key) == false)
|
||||||
|
modifierFlag = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLoraPagerKeyboard::hapticFeedback()
|
||||||
|
{
|
||||||
|
drv.setWaveform(0, 14); // strong buzz 100%
|
||||||
|
drv.setWaveform(1, 0); // end waveform
|
||||||
|
drv.go();
|
||||||
|
}
|
||||||
|
|
||||||
|
// toggle brightness of the backlight in three steps
|
||||||
|
void TLoraPagerKeyboard::toggleBacklight(bool off)
|
||||||
|
{
|
||||||
|
static uint32_t brightness = 0;
|
||||||
|
if (off) {
|
||||||
|
brightness = 0;
|
||||||
|
} else {
|
||||||
|
if (brightness == 0) {
|
||||||
|
brightness = 40;
|
||||||
|
} else if (brightness == 40) {
|
||||||
|
brightness = 127;
|
||||||
|
} else if (brightness >= 127) {
|
||||||
|
brightness = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_DEBUG("Toggle backlight: %d", brightness);
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||||
|
ledcWrite(KB_BL_PIN, brightness);
|
||||||
|
#else
|
||||||
|
ledcWrite(LEDC_BACKLIGHT_CHANNEL, brightness);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLoraPagerKeyboard::updateModifierFlag(uint8_t key)
|
||||||
|
{
|
||||||
|
if (key == modifierRightShiftKey) {
|
||||||
|
modifierFlag ^= modifierRightShift;
|
||||||
|
} else if (key == modifierSymKey) {
|
||||||
|
modifierFlag ^= modifierSym;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TLoraPagerKeyboard::isModifierKey(uint8_t key)
|
||||||
|
{
|
||||||
|
return (key == modifierRightShiftKey || key == modifierSymKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -4,9 +4,26 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TLoraPagerKeyboard();
|
TLoraPagerKeyboard();
|
||||||
void setBacklight(bool on) override{};
|
void reset(void);
|
||||||
|
void trigger(void) override;
|
||||||
|
void setBacklight(bool on) override;
|
||||||
|
virtual ~TLoraPagerKeyboard() {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void pressed(uint8_t key) override{};
|
void pressed(uint8_t key) override;
|
||||||
void released(void) override{};
|
void released(void) override;
|
||||||
|
void hapticFeedback(void);
|
||||||
|
|
||||||
|
void updateModifierFlag(uint8_t key);
|
||||||
|
bool isModifierKey(uint8_t key);
|
||||||
|
void toggleBacklight(bool off = false);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||||
|
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||||
|
int8_t last_key;
|
||||||
|
int8_t next_key;
|
||||||
|
uint32_t last_tap;
|
||||||
|
uint8_t char_idx;
|
||||||
|
int32_t tap_interval;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,14 +15,23 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
|
|||||||
this->_eventDown = eventDown;
|
this->_eventDown = eventDown;
|
||||||
this->_eventUp = eventUp;
|
this->_eventUp = eventUp;
|
||||||
this->_eventPressed = eventPressed;
|
this->_eventPressed = eventPressed;
|
||||||
|
bool isRAK = false;
|
||||||
|
#ifdef RAK_4631
|
||||||
|
isRAK = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
pinMode(pinPress, INPUT_PULLUP);
|
if (!isRAK || pinPress != 0) {
|
||||||
pinMode(this->_pinDown, INPUT_PULLUP);
|
pinMode(pinPress, INPUT_PULLUP);
|
||||||
pinMode(this->_pinUp, INPUT_PULLUP);
|
attachInterrupt(pinPress, onIntPress, RISING);
|
||||||
|
}
|
||||||
attachInterrupt(pinPress, onIntPress, RISING);
|
if (!isRAK || this->_pinDown != 0) {
|
||||||
attachInterrupt(this->_pinDown, onIntDown, RISING);
|
pinMode(this->_pinDown, INPUT_PULLUP);
|
||||||
attachInterrupt(this->_pinUp, onIntUp, RISING);
|
attachInterrupt(this->_pinDown, onIntDown, RISING);
|
||||||
|
}
|
||||||
|
if (!isRAK || this->_pinUp != 0) {
|
||||||
|
pinMode(this->_pinUp, INPUT_PULLUP);
|
||||||
|
attachInterrupt(this->_pinUp, onIntUp, RISING);
|
||||||
|
}
|
||||||
|
|
||||||
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress);
|
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress);
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,12 @@ void CardKbI2cImpl::init()
|
|||||||
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN)
|
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN)
|
||||||
if (cardkb_found.address == 0x00) {
|
if (cardkb_found.address == 0x00) {
|
||||||
LOG_DEBUG("Rescan for I2C keyboard");
|
LOG_DEBUG("Rescan for I2C keyboard");
|
||||||
uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, XPOWERS_AXP192_AXP2101_ADDRESS};
|
uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, TCA8418_KB_ADDR};
|
||||||
|
#if defined(T_LORA_PAGER)
|
||||||
|
uint8_t i2caddr_asize = sizeof(i2caddr_scan) / sizeof(i2caddr_scan[0]);
|
||||||
|
#else
|
||||||
uint8_t i2caddr_asize = 5;
|
uint8_t i2caddr_asize = 5;
|
||||||
|
#endif
|
||||||
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
|
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
|
||||||
|
|
||||||
#if WIRE_INTERFACES_COUNT == 2
|
#if WIRE_INTERFACES_COUNT == 2
|
||||||
|
|||||||
95
src/input/i2cButton.cpp
Normal file
95
src/input/i2cButton.cpp
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#include "i2cButton.h"
|
||||||
|
#include "meshUtils.h"
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
|
||||||
|
#include "MeshService.h"
|
||||||
|
#include "RadioLibInterface.h"
|
||||||
|
#include "buzz.h"
|
||||||
|
#include "input/InputBroker.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "modules/CannedMessageModule.h"
|
||||||
|
#include "modules/ExternalNotificationModule.h"
|
||||||
|
#include "power.h"
|
||||||
|
#include "sleep.h"
|
||||||
|
#ifdef ARCH_PORTDUINO
|
||||||
|
#include "platform/portduino/PortduinoGlue.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
i2cButtonThread *i2cButton;
|
||||||
|
|
||||||
|
using namespace concurrency;
|
||||||
|
|
||||||
|
extern void i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value);
|
||||||
|
|
||||||
|
extern void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value);
|
||||||
|
|
||||||
|
#define PI4IO_M_ADDR 0x43
|
||||||
|
#define getbit(x, y) ((x) >> (y)&0x01)
|
||||||
|
#define PI4IO_REG_IRQ_STA 0x13
|
||||||
|
#define PI4IO_REG_IN_STA 0x0F
|
||||||
|
#define PI4IO_REG_CHIP_RESET 0x01
|
||||||
|
|
||||||
|
i2cButtonThread::i2cButtonThread(const char *name) : OSThread(name)
|
||||||
|
{
|
||||||
|
_originName = name;
|
||||||
|
if (inputBroker)
|
||||||
|
inputBroker->registerSource(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t i2cButtonThread::runOnce()
|
||||||
|
{
|
||||||
|
static bool btn1_pressed = false;
|
||||||
|
static uint32_t press_start_time = 0;
|
||||||
|
const uint32_t LONG_PRESS_TIME = 1000;
|
||||||
|
static bool long_press_triggered = false;
|
||||||
|
|
||||||
|
uint8_t in_data;
|
||||||
|
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data);
|
||||||
|
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, in_data);
|
||||||
|
if (getbit(in_data, 0)) {
|
||||||
|
uint8_t input_state;
|
||||||
|
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IN_STA, &input_state);
|
||||||
|
|
||||||
|
if (!getbit(input_state, 0)) {
|
||||||
|
if (!btn1_pressed) {
|
||||||
|
btn1_pressed = true;
|
||||||
|
press_start_time = millis();
|
||||||
|
long_press_triggered = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (btn1_pressed) {
|
||||||
|
btn1_pressed = false;
|
||||||
|
uint32_t press_duration = millis() - press_start_time;
|
||||||
|
if (long_press_triggered) {
|
||||||
|
long_press_triggered = false;
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (press_duration < LONG_PRESS_TIME) {
|
||||||
|
InputEvent evt;
|
||||||
|
evt.source = "UserButton";
|
||||||
|
evt.inputEvent = INPUT_BROKER_USER_PRESS;
|
||||||
|
evt.kbchar = 0;
|
||||||
|
evt.touchX = 0;
|
||||||
|
evt.touchY = 0;
|
||||||
|
this->notifyObservers(&evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn1_pressed && !long_press_triggered && (millis() - press_start_time >= LONG_PRESS_TIME)) {
|
||||||
|
long_press_triggered = true;
|
||||||
|
InputEvent evt;
|
||||||
|
evt.source = "UserButton";
|
||||||
|
evt.inputEvent = INPUT_BROKER_SELECT;
|
||||||
|
evt.kbchar = 0;
|
||||||
|
evt.touchX = 0;
|
||||||
|
evt.touchY = 0;
|
||||||
|
this->notifyObservers(&evt);
|
||||||
|
}
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
18
src/input/i2cButton.h
Normal file
18
src/input/i2cButton.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "InputBroker.h"
|
||||||
|
#include "OneButton.h"
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
|
||||||
|
class i2cButtonThread : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const char *_originName;
|
||||||
|
explicit i2cButtonThread(const char *name);
|
||||||
|
int32_t runOnce() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern i2cButtonThread *i2cButton;
|
||||||
|
#endif
|
||||||
61
src/main.cpp
61
src/main.cpp
@@ -135,8 +135,9 @@ AccelerometerThread *accelerometerThread = nullptr;
|
|||||||
AudioThread *audioThread = nullptr;
|
AudioThread *audioThread = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_PCA9557
|
#ifdef USE_XL9555
|
||||||
PCA9557 IOEXP;
|
#include "ExtensionIOXL9555.hpp"
|
||||||
|
ExtensionIOXL9555 io;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if HAS_TFT
|
#if HAS_TFT
|
||||||
@@ -201,7 +202,7 @@ ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE,
|
|||||||
/// The I2C address of our Air Quality Indicator (if found)
|
/// The I2C address of our Air Quality Indicator (if found)
|
||||||
ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
|
ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
|
||||||
|
|
||||||
#ifdef T_WATCH_S3
|
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
|
||||||
Adafruit_DRV2605 drv;
|
Adafruit_DRV2605 drv;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -359,8 +360,31 @@ void setup()
|
|||||||
digitalWrite(SDCARD_CS, HIGH);
|
digitalWrite(SDCARD_CS, HIGH);
|
||||||
pinMode(PIN_EINK_CS, OUTPUT);
|
pinMode(PIN_EINK_CS, OUTPUT);
|
||||||
digitalWrite(PIN_EINK_CS, HIGH);
|
digitalWrite(PIN_EINK_CS, HIGH);
|
||||||
|
#elif defined(T_LORA_PAGER)
|
||||||
|
pinMode(LORA_CS, OUTPUT);
|
||||||
|
digitalWrite(LORA_CS, HIGH);
|
||||||
|
pinMode(SDCARD_CS, OUTPUT);
|
||||||
|
digitalWrite(SDCARD_CS, HIGH);
|
||||||
|
pinMode(TFT_CS, OUTPUT);
|
||||||
|
digitalWrite(TFT_CS, HIGH);
|
||||||
|
// io expander
|
||||||
|
io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL);
|
||||||
|
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.pinMode(EXPANDS_LORA_EN, OUTPUT);
|
||||||
|
io.digitalWrite(EXPANDS_LORA_EN, HIGH);
|
||||||
|
io.pinMode(EXPANDS_GPS_EN, OUTPUT);
|
||||||
|
io.digitalWrite(EXPANDS_GPS_EN, HIGH);
|
||||||
|
io.pinMode(EXPANDS_KB_EN, OUTPUT);
|
||||||
|
io.digitalWrite(EXPANDS_KB_EN, HIGH);
|
||||||
|
io.pinMode(EXPANDS_SD_EN, OUTPUT);
|
||||||
|
io.digitalWrite(EXPANDS_SD_EN, HIGH);
|
||||||
|
io.pinMode(EXPANDS_GPIO_EN, OUTPUT);
|
||||||
|
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
|
||||||
|
io.pinMode(EXPANDS_SD_PULLEN, INPUT);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
concurrency::hasBeenSetup = true;
|
concurrency::hasBeenSetup = true;
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0);
|
SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0);
|
||||||
@@ -394,7 +418,7 @@ void setup()
|
|||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
tv.tv_sec = time(NULL);
|
tv.tv_sec = time(NULL);
|
||||||
tv.tv_usec = 0;
|
tv.tv_usec = 0;
|
||||||
perhapsSetRTC(RTCQualityNTP, &tv);
|
perhapsSetRTC(RTCQualityDevice, &tv);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
powerMonInit();
|
powerMonInit();
|
||||||
@@ -404,6 +428,16 @@ void setup()
|
|||||||
|
|
||||||
initDeepSleep();
|
initDeepSleep();
|
||||||
|
|
||||||
|
#if defined(MODEM_POWER_EN)
|
||||||
|
pinMode(MODEM_POWER_EN, OUTPUT);
|
||||||
|
digitalWrite(MODEM_POWER_EN, LOW);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(MODEM_PWRKEY)
|
||||||
|
pinMode(MODEM_PWRKEY, OUTPUT);
|
||||||
|
digitalWrite(MODEM_PWRKEY, LOW);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(LORA_TCXO_GPIO)
|
#if defined(LORA_TCXO_GPIO)
|
||||||
pinMode(LORA_TCXO_GPIO, OUTPUT);
|
pinMode(LORA_TCXO_GPIO, OUTPUT);
|
||||||
digitalWrite(LORA_TCXO_GPIO, HIGH);
|
digitalWrite(LORA_TCXO_GPIO, HIGH);
|
||||||
@@ -509,6 +543,12 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
pinMode(LORA_CS, OUTPUT);
|
||||||
|
digitalWrite(LORA_CS, 1);
|
||||||
|
c6l_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef PIN_LCD_RESET
|
#ifdef PIN_LCD_RESET
|
||||||
// FIXME - move this someplace better, LCD is at address 0x3F
|
// FIXME - move this someplace better, LCD is at address 0x3F
|
||||||
pinMode(PIN_LCD_RESET, OUTPUT);
|
pinMode(PIN_LCD_RESET, OUTPUT);
|
||||||
@@ -706,6 +746,7 @@ void setup()
|
|||||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035);
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035);
|
||||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075);
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075);
|
||||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
|
||||||
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2561, meshtastic_TelemetrySensorType_TSL2561);
|
||||||
|
|
||||||
i2cScanner.reset();
|
i2cScanner.reset();
|
||||||
#endif
|
#endif
|
||||||
@@ -795,7 +836,7 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef T_WATCH_S3
|
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
|
||||||
drv.begin();
|
drv.begin();
|
||||||
drv.selectLibrary(1);
|
drv.selectLibrary(1);
|
||||||
// I2C trigger by sending 'go' command
|
// I2C trigger by sending 'go' command
|
||||||
@@ -841,7 +882,8 @@ void setup()
|
|||||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||||
|
|
||||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
|
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
|
||||||
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS)
|
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
|
||||||
|
defined(USE_SPISSD1306)
|
||||||
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
|
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
|
||||||
#elif defined(ARCH_PORTDUINO)
|
#elif defined(ARCH_PORTDUINO)
|
||||||
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
|
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
|
||||||
@@ -1104,7 +1146,8 @@ void setup()
|
|||||||
// Don't call screen setup until after nodedb is setup (because we need
|
// Don't call screen setup until after nodedb is setup (because we need
|
||||||
// the current region name)
|
// the current region name)
|
||||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
|
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
|
||||||
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS)
|
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
|
||||||
|
defined(USE_SPISSD1306)
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setup();
|
screen->setup();
|
||||||
#elif defined(ARCH_PORTDUINO)
|
#elif defined(ARCH_PORTDUINO)
|
||||||
@@ -1490,7 +1533,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
|
|||||||
#if ((!HAS_SCREEN || NO_EXT_GPIO) || MESHTASTIC_EXCLUDE_CANNEDMESSAGES) && !defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
|
#if ((!HAS_SCREEN || NO_EXT_GPIO) || MESHTASTIC_EXCLUDE_CANNEDMESSAGES) && !defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
|
||||||
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_CANNEDMSG_CONFIG;
|
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_CANNEDMSG_CONFIG;
|
||||||
#endif
|
#endif
|
||||||
#if NO_EXT_GPIO
|
#if NO_EXT_GPIO || MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
|
||||||
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_EXTNOTIF_CONFIG;
|
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_EXTNOTIF_CONFIG;
|
||||||
#endif
|
#endif
|
||||||
// Only edge case here is if we apply this a device with built in Accelerometer and want to detect interrupts
|
// Only edge case here is if we apply this a device with built in Accelerometer and want to detect interrupts
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ extern bool eink_found;
|
|||||||
extern bool pmu_found;
|
extern bool pmu_found;
|
||||||
extern bool isUSBPowered;
|
extern bool isUSBPowered;
|
||||||
|
|
||||||
#ifdef T_WATCH_S3
|
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
|
||||||
#include <Adafruit_DRV2605.h>
|
#include <Adafruit_DRV2605.h>
|
||||||
extern Adafruit_DRV2605 drv;
|
extern Adafruit_DRV2605 drv;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -88,4 +88,16 @@ uint32_t MemGet::getPsramSize()
|
|||||||
#else
|
#else
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayPercentHeapFree()
|
||||||
|
{
|
||||||
|
uint32_t freeHeap = memGet.getFreeHeap();
|
||||||
|
uint32_t totalHeap = memGet.getHeapSize();
|
||||||
|
if (totalHeap == 0 || totalHeap == UINT32_MAX) {
|
||||||
|
LOG_INFO("Heap size unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int percent = (int)((freeHeap * 100) / totalHeap);
|
||||||
|
LOG_INFO("Heap free: %d%% (%u/%u bytes)", percent, freeHeap, totalHeap);
|
||||||
}
|
}
|
||||||
@@ -368,7 +368,7 @@ const char *Channels::getName(size_t chIndex)
|
|||||||
// Per mesh.proto spec, if bandwidth is specified we must ignore modemPreset enum, we assume that in that case
|
// Per mesh.proto spec, if bandwidth is specified we must ignore modemPreset enum, we assume that in that case
|
||||||
// the app effed up and forgot to set channelSettings.name
|
// the app effed up and forgot to set channelSettings.name
|
||||||
if (config.lora.use_preset) {
|
if (config.lora.use_preset) {
|
||||||
channelName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
|
channelName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
|
||||||
} else {
|
} else {
|
||||||
channelName = "Custom";
|
channelName = "Custom";
|
||||||
}
|
}
|
||||||
@@ -382,7 +382,8 @@ bool Channels::isDefaultChannel(ChannelIndex chIndex)
|
|||||||
const auto &ch = getByIndex(chIndex);
|
const auto &ch = getByIndex(chIndex);
|
||||||
if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) {
|
if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) {
|
||||||
const char *name = getName(chIndex);
|
const char *name = getName(chIndex);
|
||||||
const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
|
const char *presetName =
|
||||||
|
DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
|
||||||
// Check if the name is the default derived from the modem preset
|
// Check if the name is the default derived from the modem preset
|
||||||
if (strcmp(name, presetName) == 0)
|
if (strcmp(name, presetName) == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "PointerQueue.h"
|
#include "PointerQueue.h"
|
||||||
|
#include "configuration.h" // For LOG_WARN, LOG_DEBUG, LOG_HEAP
|
||||||
|
|
||||||
template <class T> class Allocator
|
template <class T> class Allocator
|
||||||
{
|
{
|
||||||
@@ -14,13 +15,14 @@ template <class T> class Allocator
|
|||||||
Allocator() : deleter([this](T *p) { this->release(p); }) {}
|
Allocator() : deleter([this](T *p) { this->release(p); }) {}
|
||||||
virtual ~Allocator() {}
|
virtual ~Allocator() {}
|
||||||
|
|
||||||
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
|
/// Return a queable object which has been prefilled with zeros. Return nullptr if no buffer is available
|
||||||
/// Note: this method is safe to call from regular OR ISR code
|
/// Note: this method is safe to call from regular OR ISR code
|
||||||
T *allocZeroed()
|
T *allocZeroed()
|
||||||
{
|
{
|
||||||
T *p = allocZeroed(0);
|
T *p = allocZeroed(0);
|
||||||
|
if (!p) {
|
||||||
assert(p); // FIXME panic instead
|
LOG_WARN("Failed to allocate zeroed memory");
|
||||||
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,10 +41,12 @@ template <class T> class Allocator
|
|||||||
T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
|
T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
|
||||||
{
|
{
|
||||||
T *p = alloc(maxWait);
|
T *p = alloc(maxWait);
|
||||||
assert(p);
|
if (!p) {
|
||||||
|
LOG_WARN("Failed to allocate memory for copy");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (p)
|
*p = src;
|
||||||
*p = src;
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +87,11 @@ template <class T> class MemoryDynamic : public Allocator<T>
|
|||||||
/// Return a buffer for use by others
|
/// Return a buffer for use by others
|
||||||
virtual void release(T *p) override
|
virtual void release(T *p) override
|
||||||
{
|
{
|
||||||
assert(p);
|
if (p == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LOG_HEAP("Freeing 0x%x", p);
|
||||||
|
|
||||||
free(p);
|
free(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,3 +104,58 @@ template <class T> class MemoryDynamic : public Allocator<T>
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static memory pool that uses a fixed buffer instead of heap allocation
|
||||||
|
*/
|
||||||
|
template <class T, int MaxSize> class MemoryPool : public Allocator<T>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
T pool[MaxSize];
|
||||||
|
bool used[MaxSize];
|
||||||
|
|
||||||
|
public:
|
||||||
|
MemoryPool() : pool{}, used{}
|
||||||
|
{
|
||||||
|
// Arrays are now zero-initialized by member initializer list
|
||||||
|
// pool array: all elements are default-constructed (zero for POD types)
|
||||||
|
// used array: all elements are false (zero-initialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a buffer for use by others
|
||||||
|
virtual void release(T *p) override
|
||||||
|
{
|
||||||
|
if (!p) {
|
||||||
|
LOG_DEBUG("Failed to release memory, pointer is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the index of this pointer in our pool
|
||||||
|
int index = p - pool;
|
||||||
|
if (index >= 0 && index < MaxSize) {
|
||||||
|
assert(used[index]); // Should be marked as used
|
||||||
|
used[index] = false;
|
||||||
|
LOG_HEAP("Released static pool item %d at 0x%x", index, p);
|
||||||
|
} else {
|
||||||
|
LOG_WARN("Pointer 0x%x not from our pool!", p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Alloc some storage from our static pool
|
||||||
|
virtual T *alloc(TickType_t maxWait) override
|
||||||
|
{
|
||||||
|
// Find first free slot
|
||||||
|
for (int i = 0; i < MaxSize; i++) {
|
||||||
|
if (!used[i]) {
|
||||||
|
used[i] = true;
|
||||||
|
LOG_HEAP("Allocated static pool item %d at 0x%x", i, &pool[i]);
|
||||||
|
return &pool[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No free slots available - return nullptr instead of asserting
|
||||||
|
LOG_WARN("No free slots available in static memory pool!");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -85,11 +85,8 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src, const char *specificModule)
|
void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
|
||||||
{
|
{
|
||||||
if (specificModule) {
|
|
||||||
LOG_DEBUG("Calling specific module: %s", specificModule);
|
|
||||||
}
|
|
||||||
// LOG_DEBUG("In call modules");
|
// LOG_DEBUG("In call modules");
|
||||||
bool moduleFound = false;
|
bool moduleFound = false;
|
||||||
|
|
||||||
@@ -107,11 +104,6 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src, const char
|
|||||||
for (auto i = modules->begin(); i != modules->end(); ++i) {
|
for (auto i = modules->begin(); i != modules->end(); ++i) {
|
||||||
auto &pi = **i;
|
auto &pi = **i;
|
||||||
|
|
||||||
// If specificModule is provided, only call that specific module
|
|
||||||
if (specificModule && (!pi.name || strcmp(pi.name, specificModule) != 0)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pi.currentRequest = ∓
|
pi.currentRequest = ∓
|
||||||
|
|
||||||
/// We only call modules that are interested in the packet (and the message is destined to us or we are promiscious)
|
/// We only call modules that are interested in the packet (and the message is destined to us or we are promiscious)
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class MeshModule
|
|||||||
|
|
||||||
/** For use only by MeshService
|
/** For use only by MeshService
|
||||||
*/
|
*/
|
||||||
static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO, const char *specificModule = nullptr);
|
static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO);
|
||||||
|
|
||||||
static std::vector<MeshModule *> GetMeshModulesWithUIFrames(int startIndex);
|
static std::vector<MeshModule *> GetMeshModulesWithUIFrames(int startIndex);
|
||||||
static void observeUIEvents(Observer<const UIFrameEvent *> *observer);
|
static void observeUIEvents(Observer<const UIFrameEvent *> *observer);
|
||||||
|
|||||||
@@ -46,11 +46,14 @@ the new node can build its node db)
|
|||||||
|
|
||||||
MeshService *service;
|
MeshService *service;
|
||||||
|
|
||||||
static MemoryDynamic<meshtastic_MqttClientProxyMessage> staticMqttClientProxyMessagePool;
|
#define MAX_MQTT_PROXY_MESSAGES 16
|
||||||
|
static MemoryPool<meshtastic_MqttClientProxyMessage, MAX_MQTT_PROXY_MESSAGES> staticMqttClientProxyMessagePool;
|
||||||
|
|
||||||
static MemoryDynamic<meshtastic_QueueStatus> staticQueueStatusPool;
|
#define MAX_QUEUE_STATUS 4
|
||||||
|
static MemoryPool<meshtastic_QueueStatus, MAX_QUEUE_STATUS> staticQueueStatusPool;
|
||||||
|
|
||||||
static MemoryDynamic<meshtastic_ClientNotification> staticClientNotificationPool;
|
#define MAX_CLIENT_NOTIFICATIONS 4
|
||||||
|
static MemoryPool<meshtastic_ClientNotification, MAX_CLIENT_NOTIFICATIONS> staticClientNotificationPool;
|
||||||
|
|
||||||
Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool;
|
Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool;
|
||||||
|
|
||||||
@@ -61,8 +64,10 @@ Allocator<meshtastic_QueueStatus> &queueStatusPool = staticQueueStatusPool;
|
|||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
|
|
||||||
MeshService::MeshService()
|
MeshService::MeshService()
|
||||||
: toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE),
|
#ifdef ARCH_PORTDUINO
|
||||||
toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2)
|
: toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_QUEUESTATUS_TOPHONE),
|
||||||
|
toPhoneMqttProxyQueue(MAX_RX_MQTTPROXY_TOPHONE), toPhoneClientNotificationQueue(MAX_RX_NOTIFICATION_TOPHONE)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
lastQueueStatus = {0, 0, 16, 0};
|
lastQueueStatus = {0, 0, 16, 0};
|
||||||
}
|
}
|
||||||
@@ -191,8 +196,10 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
|
|||||||
// (so we update our nodedb for the local node)
|
// (so we update our nodedb for the local node)
|
||||||
|
|
||||||
// Send the packet into the mesh
|
// Send the packet into the mesh
|
||||||
|
DEBUG_HEAP_BEFORE;
|
||||||
sendToMesh(packetPool.allocCopy(p), RX_SRC_USER);
|
auto a = packetPool.allocCopy(p);
|
||||||
|
DEBUG_HEAP_AFTER("MeshService::handleToRadio", a);
|
||||||
|
sendToMesh(a, RX_SRC_USER);
|
||||||
|
|
||||||
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
|
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
|
||||||
if (loopback) {
|
if (loopback) {
|
||||||
@@ -248,7 +255,11 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((res == ERRNO_OK || res == ERRNO_SHOULD_RELEASE) && ccToPhone) { // Check if p is not released in case it couldn't be sent
|
if ((res == ERRNO_OK || res == ERRNO_SHOULD_RELEASE) && ccToPhone) { // Check if p is not released in case it couldn't be sent
|
||||||
sendToPhone(packetPool.allocCopy(*p));
|
DEBUG_HEAP_BEFORE;
|
||||||
|
auto a = packetPool.allocCopy(*p);
|
||||||
|
DEBUG_HEAP_AFTER("MeshService::sendToMesh", a);
|
||||||
|
|
||||||
|
sendToPhone(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Router may ask us to release the packet if it wasn't sent
|
// Router may ask us to release the packet if it wasn't sent
|
||||||
|
|||||||
@@ -9,7 +9,12 @@
|
|||||||
#include "MeshRadio.h"
|
#include "MeshRadio.h"
|
||||||
#include "MeshTypes.h"
|
#include "MeshTypes.h"
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
|
#ifdef ARCH_PORTDUINO
|
||||||
#include "PointerQueue.h"
|
#include "PointerQueue.h"
|
||||||
|
#else
|
||||||
|
#include "StaticPointerQueue.h"
|
||||||
|
#endif
|
||||||
|
#include "mesh-pb-constants.h"
|
||||||
#if defined(ARCH_PORTDUINO)
|
#if defined(ARCH_PORTDUINO)
|
||||||
#include "../platform/portduino/SimRadio.h"
|
#include "../platform/portduino/SimRadio.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -37,16 +42,32 @@ class MeshService
|
|||||||
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
|
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
|
||||||
/// we never hang because android hasn't been there in a while
|
/// we never hang because android hasn't been there in a while
|
||||||
/// FIXME - save this to flash on deep sleep
|
/// FIXME - save this to flash on deep sleep
|
||||||
|
#ifdef ARCH_PORTDUINO
|
||||||
PointerQueue<meshtastic_MeshPacket> toPhoneQueue;
|
PointerQueue<meshtastic_MeshPacket> toPhoneQueue;
|
||||||
|
#else
|
||||||
|
StaticPointerQueue<meshtastic_MeshPacket, MAX_RX_TOPHONE> toPhoneQueue;
|
||||||
|
#endif
|
||||||
|
|
||||||
// keep list of QueueStatus packets to be send to the phone
|
// keep list of QueueStatus packets to be send to the phone
|
||||||
|
#ifdef ARCH_PORTDUINO
|
||||||
PointerQueue<meshtastic_QueueStatus> toPhoneQueueStatusQueue;
|
PointerQueue<meshtastic_QueueStatus> toPhoneQueueStatusQueue;
|
||||||
|
#else
|
||||||
|
StaticPointerQueue<meshtastic_QueueStatus, MAX_RX_QUEUESTATUS_TOPHONE> toPhoneQueueStatusQueue;
|
||||||
|
#endif
|
||||||
|
|
||||||
// keep list of MqttClientProxyMessages to be send to the client for delivery
|
// keep list of MqttClientProxyMessages to be send to the client for delivery
|
||||||
|
#ifdef ARCH_PORTDUINO
|
||||||
PointerQueue<meshtastic_MqttClientProxyMessage> toPhoneMqttProxyQueue;
|
PointerQueue<meshtastic_MqttClientProxyMessage> toPhoneMqttProxyQueue;
|
||||||
|
#else
|
||||||
|
StaticPointerQueue<meshtastic_MqttClientProxyMessage, MAX_RX_MQTTPROXY_TOPHONE> toPhoneMqttProxyQueue;
|
||||||
|
#endif
|
||||||
|
|
||||||
// keep list of ClientNotifications to be send to the client (phone)
|
// keep list of ClientNotifications to be send to the client (phone)
|
||||||
|
#ifdef ARCH_PORTDUINO
|
||||||
PointerQueue<meshtastic_ClientNotification> toPhoneClientNotificationQueue;
|
PointerQueue<meshtastic_ClientNotification> toPhoneClientNotificationQueue;
|
||||||
|
#else
|
||||||
|
StaticPointerQueue<meshtastic_ClientNotification, MAX_RX_NOTIFICATION_TOPHONE> toPhoneClientNotificationQueue;
|
||||||
|
#endif
|
||||||
|
|
||||||
// This holds the last QueueStatus send
|
// This holds the last QueueStatus send
|
||||||
meshtastic_QueueStatus lastQueueStatus;
|
meshtastic_QueueStatus lastQueueStatus;
|
||||||
|
|||||||
@@ -34,8 +34,11 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
|||||||
bool weWereNextHop = false;
|
bool weWereNextHop = false;
|
||||||
if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
|
if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
|
||||||
printPacket("Ignore dupe incoming msg", p);
|
printPacket("Ignore dupe incoming msg", p);
|
||||||
rxDupe++;
|
|
||||||
stopRetransmission(p->from, p->id);
|
if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
|
||||||
|
rxDupe++;
|
||||||
|
stopRetransmission(p->from, p->id);
|
||||||
|
}
|
||||||
|
|
||||||
// If it was a fallback to flooding, try to relay again
|
// If it was a fallback to flooding, try to relay again
|
||||||
if (wasFallback) {
|
if (wasFallback) {
|
||||||
@@ -71,10 +74,11 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
|
|||||||
if (p->from != 0) {
|
if (p->from != 0) {
|
||||||
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
|
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
|
||||||
if (origTx) {
|
if (origTx) {
|
||||||
// Either relayer of ACK was also a relayer of the packet, or we were the relayer and the ACK came directly from
|
// Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came directly
|
||||||
// the destination
|
// from the destination
|
||||||
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
|
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
|
||||||
(wasRelayer(ourRelayID, p->decoded.request_id, p->to) && p->hop_start != 0 && p->hop_start == p->hop_limit)) {
|
(p->hop_start != 0 && p->hop_start == p->hop_limit &&
|
||||||
|
wasSoleRelayer(ourRelayID, p->decoded.request_id, p->to))) {
|
||||||
if (origTx->next_hop != p->relay_node) { // Not already set
|
if (origTx->next_hop != p->relay_node) { // Not already set
|
||||||
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
|
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
|
||||||
origTx->next_hop = p->relay_node;
|
origTx->next_hop = p->relay_node;
|
||||||
@@ -171,12 +175,18 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
|
|||||||
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
|
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
|
||||||
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
|
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
|
||||||
cancelSending(getFrom(p), p->id);
|
cancelSending(getFrom(p), p->id);
|
||||||
// now free the pooled copy for retransmission too
|
|
||||||
packetPool.release(p);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
auto numErased = pending.erase(key);
|
||||||
assert(numErased == 1);
|
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.
|
||||||
|
packetPool.release(p);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
|||||||
config.bluetooth.fixed_pin = defaultBLEPin;
|
config.bluetooth.fixed_pin = defaultBLEPin;
|
||||||
|
|
||||||
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
|
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
|
||||||
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS)
|
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306)
|
||||||
bool hasScreen = true;
|
bool hasScreen = true;
|
||||||
#ifdef HELTEC_MESH_NODE_T114
|
#ifdef HELTEC_MESH_NODE_T114
|
||||||
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);
|
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);
|
||||||
@@ -830,6 +830,15 @@ void NodeDB::installDefaultModuleConfig()
|
|||||||
moduleConfig.external_notification.alert_message = true;
|
moduleConfig.external_notification.alert_message = true;
|
||||||
moduleConfig.external_notification.output_ms = 1000;
|
moduleConfig.external_notification.output_ms = 1000;
|
||||||
moduleConfig.external_notification.nag_timeout = 60;
|
moduleConfig.external_notification.nag_timeout = 60;
|
||||||
|
#endif
|
||||||
|
#ifdef T_LORA_PAGER
|
||||||
|
moduleConfig.canned_message.updown1_enabled = true;
|
||||||
|
moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A;
|
||||||
|
moduleConfig.canned_message.inputbroker_pin_b = ROTARY_B;
|
||||||
|
moduleConfig.canned_message.inputbroker_pin_press = ROTARY_PRESS;
|
||||||
|
moduleConfig.canned_message.inputbroker_event_cw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(28);
|
||||||
|
moduleConfig.canned_message.inputbroker_event_ccw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(29);
|
||||||
|
moduleConfig.canned_message.inputbroker_event_press = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
|
||||||
#endif
|
#endif
|
||||||
moduleConfig.has_canned_message = true;
|
moduleConfig.has_canned_message = true;
|
||||||
#if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT
|
#if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT
|
||||||
@@ -1702,10 +1711,10 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
|||||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||||
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
||||||
{
|
{
|
||||||
// if (mp.from == getNodeNum()) {
|
if (mp.from == getNodeNum()) {
|
||||||
// LOG_DEBUG("Ignore update from self");
|
LOG_DEBUG("Ignore update from self");
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
|
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
|
||||||
LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);
|
LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);
|
||||||
|
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ void PacketHistory::insert(const PacketRecord &r)
|
|||||||
|
|
||||||
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
|
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
|
||||||
* @return true if node was indeed a relayer, false if not */
|
* @return true if node was indeed a relayer, false if not */
|
||||||
bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
|
bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole)
|
||||||
{
|
{
|
||||||
if (!initOk()) {
|
if (!initOk()) {
|
||||||
LOG_ERROR("PacketHistory - wasRelayer: NOT INITIALIZED!");
|
LOG_ERROR("PacketHistory - wasRelayer: NOT INITIALIZED!");
|
||||||
@@ -322,27 +322,42 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const N
|
|||||||
found->sender, found->id, found->next_hop, millis() - found->rxTimeMsec, found->relayed_by[0], found->relayed_by[1],
|
found->sender, found->id, found->next_hop, millis() - found->rxTimeMsec, found->relayed_by[0], found->relayed_by[1],
|
||||||
found->relayed_by[2], relayer);
|
found->relayed_by[2], relayer);
|
||||||
#endif
|
#endif
|
||||||
return wasRelayer(relayer, *found);
|
return wasRelayer(relayer, *found, wasSole);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if a certain node was a relayer of a packet in the history given iterator
|
/* Check if a certain node was a relayer of a packet in the history given iterator
|
||||||
* @return true if node was indeed a relayer, false if not */
|
* @return true if node was indeed a relayer, false if not */
|
||||||
bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r)
|
bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole)
|
||||||
{
|
{
|
||||||
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
|
bool found = false;
|
||||||
|
bool other_present = false;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < NUM_RELAYERS; ++i) {
|
||||||
if (r.relayed_by[i] == relayer) {
|
if (r.relayed_by[i] == relayer) {
|
||||||
#if VERBOSE_PACKET_HISTORY
|
found = true;
|
||||||
LOG_DEBUG("Packet History - was rel.PR.: s=%08x id=%08x rls=%02x %02x %02x / rl=%02x? YES", r.sender, r.id,
|
} else if (r.relayed_by[i] != 0) {
|
||||||
r.relayed_by[0], r.relayed_by[1], r.relayed_by[2], relayer);
|
other_present = true;
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wasSole) {
|
||||||
|
*wasSole = (found && !other_present);
|
||||||
|
}
|
||||||
|
|
||||||
#if VERBOSE_PACKET_HISTORY
|
#if VERBOSE_PACKET_HISTORY
|
||||||
LOG_DEBUG("Packet History - was rel.PR.: s=%08x id=%08x rls=%02x %02x %02x / rl=%02x? NO", r.sender, r.id, r.relayed_by[0],
|
LOG_DEBUG("Packet History - was rel.PR.: s=%08x id=%08x rls=%02x %02x %02x / rl=%02x? NO", r.sender, r.id, r.relayed_by[0],
|
||||||
r.relayed_by[1], r.relayed_by[2], relayer);
|
r.relayed_by[1], r.relayed_by[2], relayer);
|
||||||
#endif
|
#endif
|
||||||
return false;
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
|
||||||
|
bool PacketHistory::wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
|
||||||
|
{
|
||||||
|
bool wasSole = false;
|
||||||
|
wasRelayer(relayer, id, sender, &wasSole);
|
||||||
|
return wasSole;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
|
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ class PacketHistory
|
|||||||
void insert(const PacketRecord &r); // Insert or replace a packet record in the history
|
void insert(const PacketRecord &r); // Insert or replace a packet record in the history
|
||||||
|
|
||||||
/* Check if a certain node was a relayer of a packet in the history given iterator
|
/* Check if a certain node was a relayer of a packet in the history given iterator
|
||||||
|
* If wasSole is not nullptr, it will be set to true if the relayer was the only relayer of that packet
|
||||||
* @return true if node was indeed a relayer, false if not */
|
* @return true if node was indeed a relayer, false if not */
|
||||||
bool wasRelayer(const uint8_t relayer, const PacketRecord &r);
|
bool wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole = nullptr);
|
||||||
|
|
||||||
PacketHistory(const PacketHistory &); // non construction-copyable
|
PacketHistory(const PacketHistory &); // non construction-copyable
|
||||||
PacketHistory &operator=(const PacketHistory &); // non copyable
|
PacketHistory &operator=(const PacketHistory &); // non copyable
|
||||||
@@ -54,8 +55,12 @@ class PacketHistory
|
|||||||
bool *weWereNextHop = nullptr);
|
bool *weWereNextHop = nullptr);
|
||||||
|
|
||||||
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
|
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
|
||||||
|
* If wasSole is not nullptr, it will be set to true if the relayer was the only relayer of that packet
|
||||||
* @return true if node was indeed a relayer, false if not */
|
* @return true if node was indeed a relayer, false if not */
|
||||||
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
|
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole = nullptr);
|
||||||
|
|
||||||
|
// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
|
||||||
|
bool wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
|
||||||
|
|
||||||
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
|
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
|
||||||
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
|
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ void PhoneAPI::close()
|
|||||||
config_nonce = 0;
|
config_nonce = 0;
|
||||||
config_state = 0;
|
config_state = 0;
|
||||||
pauseBluetoothLogging = false;
|
pauseBluetoothLogging = false;
|
||||||
|
heartbeatReceived = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -586,7 +586,8 @@ void RadioInterface::applyModemConfig()
|
|||||||
|
|
||||||
// Check if we use the default frequency slot
|
// Check if we use the default frequency slot
|
||||||
RadioInterface::uses_default_frequency_slot =
|
RadioInterface::uses_default_frequency_slot =
|
||||||
channel_num == hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false)) % numChannels;
|
channel_num ==
|
||||||
|
hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % numChannels;
|
||||||
|
|
||||||
// Old frequency selection formula
|
// Old frequency selection formula
|
||||||
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num);
|
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "Default.h"
|
#include "Default.h"
|
||||||
#include "MeshTypes.h"
|
#include "MeshTypes.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include "memGet.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "modules/NodeInfoModule.h"
|
#include "modules/NodeInfoModule.h"
|
||||||
#include "modules/RoutingModule.h"
|
#include "modules/RoutingModule.h"
|
||||||
@@ -21,8 +22,10 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
|
|||||||
if (p->hop_limit == 0) {
|
if (p->hop_limit == 0) {
|
||||||
p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
|
p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
|
||||||
}
|
}
|
||||||
|
DEBUG_HEAP_BEFORE;
|
||||||
auto copy = packetPool.allocCopy(*p);
|
auto copy = packetPool.allocCopy(*p);
|
||||||
|
DEBUG_HEAP_AFTER("ReliableRouter::send", copy);
|
||||||
|
|
||||||
startRetransmission(copy, NUM_RELIABLE_RETX);
|
startRetransmission(copy, NUM_RELIABLE_RETX);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +61,10 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
|||||||
// marked as wantAck
|
// marked as wantAck
|
||||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, old->packet->channel);
|
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, old->packet->channel);
|
||||||
|
|
||||||
stopRetransmission(key);
|
// Only stop retransmissions if the rebroadcast came via LoRa
|
||||||
|
if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
|
||||||
|
stopRetransmission(key);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_DEBUG("Didn't find pending packet");
|
LOG_DEBUG("Didn't find pending packet");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "detect/LoRaRadioType.h"
|
#include "detect/LoRaRadioType.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
@@ -27,14 +28,24 @@
|
|||||||
|
|
||||||
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
|
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
|
||||||
// And every TX packet might have a retransmission packet or an ack alive at any moment
|
// And every TX packet might have a retransmission packet or an ack alive at any moment
|
||||||
|
|
||||||
|
#ifdef ARCH_PORTDUINO
|
||||||
|
// Portduino (native) targets can use dynamic memory pools with runtime-configurable sizes
|
||||||
#define MAX_PACKETS \
|
#define MAX_PACKETS \
|
||||||
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
|
(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)
|
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
||||||
|
|
||||||
// static MemoryPool<MeshPacket> staticPool(MAX_PACKETS);
|
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
|
||||||
static MemoryDynamic<meshtastic_MeshPacket> staticPool;
|
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
|
||||||
|
#else
|
||||||
|
// Embedded targets use static memory pools with compile-time constants
|
||||||
|
#define MAX_PACKETS_STATIC \
|
||||||
|
(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 MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
|
||||||
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
|
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
|
||||||
|
#endif
|
||||||
|
|
||||||
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
|
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
|
||||||
|
|
||||||
@@ -275,7 +286,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
|||||||
// If the packet is not yet encrypted, do so now
|
// If the packet is not yet encrypted, do so now
|
||||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||||
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
|
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
|
||||||
|
|
||||||
|
DEBUG_HEAP_BEFORE;
|
||||||
meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p);
|
meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p);
|
||||||
|
DEBUG_HEAP_AFTER("Router::send", p_decoded);
|
||||||
|
|
||||||
auto encodeResult = perhapsEncode(p);
|
auto encodeResult = perhapsEncode(p);
|
||||||
if (encodeResult != meshtastic_Routing_Error_NONE) {
|
if (encodeResult != meshtastic_Routing_Error_NONE) {
|
||||||
@@ -529,8 +543,9 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
|||||||
#endif
|
#endif
|
||||||
// Don't use PKC with Ham mode
|
// Don't use PKC with Ham mode
|
||||||
!owner.is_licensed &&
|
!owner.is_licensed &&
|
||||||
// Don't use PKC if it's not explicitly requested and a non-primary channel is requested
|
// Don't use PKC on 'serial' or 'gpio' channels unless explicitly requested
|
||||||
!(p->pki_encrypted != true && p->channel > 0) &&
|
!(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 ||
|
||||||
|
strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) &&
|
||||||
// Check for valid keys and single node destination
|
// Check for valid keys and single node destination
|
||||||
config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
|
config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
|
||||||
// Check for a known public key for the destination
|
// Check for a known public key for the destination
|
||||||
@@ -561,7 +576,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
|||||||
// Now that we are encrypting the packet channel should be the hash (no longer the index)
|
// Now that we are encrypting the packet channel should be the hash (no longer the index)
|
||||||
p->channel = hash;
|
p->channel = hash;
|
||||||
if (hash < 0) {
|
if (hash < 0) {
|
||||||
// No suitable channel could be found for sending
|
// No suitable channel could be found for
|
||||||
return meshtastic_Routing_Error_NO_CHANNEL;
|
return meshtastic_Routing_Error_NO_CHANNEL;
|
||||||
}
|
}
|
||||||
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
|
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
|
||||||
@@ -577,7 +592,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
|||||||
// Now that we are encrypting the packet channel should be the hash (no longer the index)
|
// Now that we are encrypting the packet channel should be the hash (no longer the index)
|
||||||
p->channel = hash;
|
p->channel = hash;
|
||||||
if (hash < 0) {
|
if (hash < 0) {
|
||||||
// No suitable channel could be found for sending
|
// No suitable channel could be found for
|
||||||
return meshtastic_Routing_Error_NO_CHANNEL;
|
return meshtastic_Routing_Error_NO_CHANNEL;
|
||||||
}
|
}
|
||||||
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
|
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
|
||||||
@@ -606,8 +621,11 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
|||||||
bool skipHandle = false;
|
bool skipHandle = false;
|
||||||
// Also, we should set the time from the ISR and it should have msec level resolution
|
// Also, we should set the time from the ISR and it should have msec level resolution
|
||||||
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
|
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
|
||||||
|
|
||||||
// Store a copy of encrypted packet for MQTT
|
// Store a copy of encrypted packet for MQTT
|
||||||
|
DEBUG_HEAP_BEFORE;
|
||||||
meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p);
|
meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p);
|
||||||
|
DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted);
|
||||||
|
|
||||||
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
||||||
auto decodedState = perhapsDecode(p);
|
auto decodedState = perhapsDecode(p);
|
||||||
@@ -655,7 +673,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
|||||||
|
|
||||||
// call modules here
|
// call modules here
|
||||||
// If this could be a spoofed packet, don't let the modules see it.
|
// If this could be a spoofed packet, don't let the modules see it.
|
||||||
if (!skipHandle && p->from != nodeDB->getNodeNum()) {
|
if (!skipHandle) {
|
||||||
MeshModule::callModules(*p, src);
|
MeshModule::callModules(*p, src);
|
||||||
|
|
||||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||||
@@ -669,8 +687,6 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
|||||||
!isFromUs(p) && mqtt)
|
!isFromUs(p) && mqtt)
|
||||||
mqtt->onSend(*p_encrypted, *p, p->channel);
|
mqtt->onSend(*p_encrypted, *p, p->channel);
|
||||||
#endif
|
#endif
|
||||||
} else if (p->from == nodeDB->getNodeNum() && !skipHandle) {
|
|
||||||
MeshModule::callModules(*p, src, ROUTING_MODULE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
packetPool.release(p_encrypted); // Release the encrypted packet
|
packetPool.release(p_encrypted); // Release the encrypted packet
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#include "STM32WLE5JCInterface.h"
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#ifdef ARCH_STM32WL
|
||||||
|
#include "STM32WLE5JCInterface.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
|
|
||||||
#ifndef STM32WLx_MAX_POWER
|
#ifndef STM32WLx_MAX_POWER
|
||||||
#define STM32WLx_MAX_POWER 22
|
#define STM32WLx_MAX_POWER 22
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ARCH_STM32WL
|
|
||||||
|
|
||||||
STM32WLE5JCInterface::STM32WLE5JCInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq,
|
STM32WLE5JCInterface::STM32WLE5JCInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq,
|
||||||
RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy)
|
RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy)
|
||||||
: SX126xInterface(hal, cs, irq, rst, busy)
|
: SX126xInterface(hal, cs, irq, rst, busy)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "SX126xInterface.h"
|
|
||||||
|
|
||||||
#ifdef ARCH_STM32WL
|
#ifdef ARCH_STM32WL
|
||||||
|
#include "SX126xInterface.h"
|
||||||
|
#include "rfswitch.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Our adapter for STM32WLE5JC radios
|
* Our adapter for STM32WLE5JC radios
|
||||||
@@ -16,13 +16,4 @@ class STM32WLE5JCInterface : public SX126xInterface<STM32WLx>
|
|||||||
virtual bool init() override;
|
virtual bool init() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* https://wiki.seeedstudio.com/LoRa-E5_STM32WLE5JC_Module/
|
|
||||||
* Wio-E5 module ONLY transmits through RFO_HP
|
|
||||||
* Receive: PA4=1, PA5=0
|
|
||||||
* Transmit(high output power, SMPS mode): PA4=0, PA5=1 */
|
|
||||||
static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PA4, PA5, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
|
|
||||||
|
|
||||||
static const Module::RfSwitchMode_t rfswitch_table[4] = {
|
|
||||||
{STM32WLx::MODE_IDLE, {LOW, LOW}}, {STM32WLx::MODE_RX, {HIGH, LOW}}, {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, END_OF_MODE_TABLE};
|
|
||||||
|
|
||||||
#endif // ARCH_STM32WL
|
#endif // ARCH_STM32WL
|
||||||
77
src/mesh/StaticPointerQueue.h
Normal file
77
src/mesh/StaticPointerQueue.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "concurrency/OSThread.h"
|
||||||
|
#include "freertosinc.h"
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static circular buffer queue for pointers.
|
||||||
|
* This provides the same interface as PointerQueue but uses a statically allocated
|
||||||
|
* buffer instead of dynamic allocation.
|
||||||
|
*/
|
||||||
|
template <class T, int MaxElements> class StaticPointerQueue
|
||||||
|
{
|
||||||
|
static_assert(MaxElements > 0, "MaxElements must be greater than 0");
|
||||||
|
|
||||||
|
T *buffer[MaxElements];
|
||||||
|
int head = 0;
|
||||||
|
int tail = 0;
|
||||||
|
int count = 0;
|
||||||
|
concurrency::OSThread *reader = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
StaticPointerQueue()
|
||||||
|
{
|
||||||
|
// Initialize all buffer elements to nullptr to silence warnings and ensure clean state
|
||||||
|
for (int i = 0; i < MaxElements; i++) {
|
||||||
|
buffer[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int numFree() const { return MaxElements - count; }
|
||||||
|
|
||||||
|
bool isEmpty() const { return count == 0; }
|
||||||
|
|
||||||
|
int numUsed() const { return count; }
|
||||||
|
|
||||||
|
bool enqueue(T *x, TickType_t maxWait = portMAX_DELAY)
|
||||||
|
{
|
||||||
|
if (count >= MaxElements) {
|
||||||
|
return false; // Queue is full
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader) {
|
||||||
|
reader->setInterval(0);
|
||||||
|
concurrency::mainDelay.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[tail] = x;
|
||||||
|
tail = (tail + 1) % MaxElements;
|
||||||
|
count++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dequeue(T **p, TickType_t maxWait = portMAX_DELAY)
|
||||||
|
{
|
||||||
|
if (count == 0) {
|
||||||
|
return false; // Queue is empty
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = buffer[head];
|
||||||
|
head = (head + 1) % MaxElements;
|
||||||
|
count--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a ptr or null if the queue was empty
|
||||||
|
T *dequeuePtr(TickType_t maxWait = portMAX_DELAY)
|
||||||
|
{
|
||||||
|
T *p;
|
||||||
|
return dequeue(&p, maxWait) ? p : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setReader(concurrency::OSThread *t) { reader = t; }
|
||||||
|
|
||||||
|
// For compatibility with PointerQueue interface
|
||||||
|
int getMaxLen() const { return MaxElements; }
|
||||||
|
};
|
||||||
@@ -16,6 +16,95 @@ int32_t StreamAPI::runOncePart()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen)
|
||||||
|
{
|
||||||
|
auto result = readStream(buf, bufLen);
|
||||||
|
writeStream();
|
||||||
|
checkConnectionTimeout();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read any rx chars from the link and call handleRecStream
|
||||||
|
*/
|
||||||
|
int32_t StreamAPI::readStream(char *buf, uint16_t bufLen)
|
||||||
|
{
|
||||||
|
if (bufLen < 1) {
|
||||||
|
// Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time
|
||||||
|
bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000);
|
||||||
|
return recentRx ? 5 : 250;
|
||||||
|
} else {
|
||||||
|
handleRecStream(buf, bufLen);
|
||||||
|
// we had bytes available this time, so assume we might have them next time also
|
||||||
|
lastRxMsec = millis();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* call getFromRadio() and deliver encapsulated packets to the Stream
|
||||||
|
*/
|
||||||
|
void StreamAPI::writeStream()
|
||||||
|
{
|
||||||
|
if (canWrite) {
|
||||||
|
uint32_t len;
|
||||||
|
do {
|
||||||
|
// Send every packet we can
|
||||||
|
len = getFromRadio(txBuf + HEADER_LEN);
|
||||||
|
emitTxBuffer(len);
|
||||||
|
} while (len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen)
|
||||||
|
{
|
||||||
|
uint16_t index = 0;
|
||||||
|
while (bufLen > index) { // Currently we never want to block
|
||||||
|
int cInt = buf[index++];
|
||||||
|
if (cInt < 0)
|
||||||
|
break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit
|
||||||
|
// arduino
|
||||||
|
|
||||||
|
uint8_t c = (uint8_t)cInt;
|
||||||
|
|
||||||
|
// Use the read pointer for a little state machine, first look for framing, then length bytes, then payload
|
||||||
|
size_t ptr = rxPtr;
|
||||||
|
|
||||||
|
rxPtr++; // assume we will probably advance the rxPtr
|
||||||
|
rxBuf[ptr] = c; // store all bytes (including framing)
|
||||||
|
|
||||||
|
// console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c);
|
||||||
|
|
||||||
|
if (ptr == 0) { // looking for START1
|
||||||
|
if (c != START1)
|
||||||
|
rxPtr = 0; // failed to find framing
|
||||||
|
} else if (ptr == 1) { // looking for START2
|
||||||
|
if (c != START2)
|
||||||
|
rxPtr = 0; // failed to find framing
|
||||||
|
} else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing
|
||||||
|
uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing
|
||||||
|
|
||||||
|
// console->printf("len %d\n", len);
|
||||||
|
|
||||||
|
if (ptr == HEADER_LEN - 1) {
|
||||||
|
// we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid
|
||||||
|
// protobuf also)
|
||||||
|
if (len > MAX_TO_FROM_RADIO_SIZE)
|
||||||
|
rxPtr = 0; // length is bogus, restart search for framing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rxPtr != 0) // Is packet still considered 'good'?
|
||||||
|
if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload?
|
||||||
|
rxPtr = 0; // start over again on the next packet
|
||||||
|
|
||||||
|
// If we didn't just fail the packet and we now have the right # of bytes, parse it
|
||||||
|
handleToRadio(rxBuf + HEADER_LEN, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read any rx chars from the link and call handleToRadio
|
* Read any rx chars from the link and call handleToRadio
|
||||||
*/
|
*/
|
||||||
@@ -76,21 +165,6 @@ int32_t StreamAPI::readStream()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* call getFromRadio() and deliver encapsulated packets to the Stream
|
|
||||||
*/
|
|
||||||
void StreamAPI::writeStream()
|
|
||||||
{
|
|
||||||
if (canWrite) {
|
|
||||||
uint32_t len;
|
|
||||||
do {
|
|
||||||
// Send every packet we can
|
|
||||||
len = getFromRadio(txBuf + HEADER_LEN);
|
|
||||||
emitTxBuffer(len);
|
|
||||||
} while (len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the current txBuffer over our stream
|
* Send the current txBuffer over our stream
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -50,12 +50,15 @@ class StreamAPI : public PhoneAPI
|
|||||||
* phone.
|
* phone.
|
||||||
*/
|
*/
|
||||||
virtual int32_t runOncePart();
|
virtual int32_t runOncePart();
|
||||||
|
virtual int32_t runOncePart(char *buf,uint16_t bufLen);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Read any rx chars from the link and call handleToRadio
|
* Read any rx chars from the link and call handleToRadio
|
||||||
*/
|
*/
|
||||||
int32_t readStream();
|
int32_t readStream();
|
||||||
|
int32_t readStream(char *buf,uint16_t bufLen);
|
||||||
|
int32_t handleRecStream(char *buf,uint16_t bufLen);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* call getFromRadio() and deliver encapsulated packets to the Stream
|
* call getFromRadio() and deliver encapsulated packets to the Stream
|
||||||
|
|||||||
@@ -64,7 +64,12 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
|
|||||||
in areas not already covered by other routers, or to bridge around problematic terrain,
|
in areas not already covered by other routers, or to bridge around problematic terrain,
|
||||||
but should not be given priority over other routers in order to avoid unnecessaraily
|
but should not be given priority over other routers in order to avoid unnecessaraily
|
||||||
consuming hops. */
|
consuming hops. */
|
||||||
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11
|
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11,
|
||||||
|
/* Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
|
||||||
|
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
|
||||||
|
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
|
||||||
|
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node. */
|
||||||
|
meshtastic_Config_DeviceConfig_Role_CLIENT_BASE = 12
|
||||||
} meshtastic_Config_DeviceConfig_Role;
|
} meshtastic_Config_DeviceConfig_Role;
|
||||||
|
|
||||||
/* Defines the device's behavior for how messages are rebroadcast */
|
/* Defines the device's behavior for how messages are rebroadcast */
|
||||||
@@ -646,8 +651,8 @@ extern "C" {
|
|||||||
|
|
||||||
/* Helper constants for enums */
|
/* Helper constants for enums */
|
||||||
#define _meshtastic_Config_DeviceConfig_Role_MIN meshtastic_Config_DeviceConfig_Role_CLIENT
|
#define _meshtastic_Config_DeviceConfig_Role_MIN meshtastic_Config_DeviceConfig_Role_CLIENT
|
||||||
#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_ROUTER_LATE
|
#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_CLIENT_BASE
|
||||||
#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_ROUTER_LATE+1))
|
#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_CLIENT_BASE+1))
|
||||||
|
|
||||||
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL
|
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL
|
||||||
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY
|
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ typedef enum _meshtastic_Language {
|
|||||||
meshtastic_Language_UKRAINIAN = 16,
|
meshtastic_Language_UKRAINIAN = 16,
|
||||||
/* Bulgarian */
|
/* Bulgarian */
|
||||||
meshtastic_Language_BULGARIAN = 17,
|
meshtastic_Language_BULGARIAN = 17,
|
||||||
|
/* Czech */
|
||||||
|
meshtastic_Language_CZECH = 18,
|
||||||
/* Simplified Chinese (experimental) */
|
/* Simplified Chinese (experimental) */
|
||||||
meshtastic_Language_SIMPLIFIED_CHINESE = 30,
|
meshtastic_Language_SIMPLIFIED_CHINESE = 30,
|
||||||
/* Traditional Chinese (experimental) */
|
/* Traditional Chinese (experimental) */
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
|
|||||||
/* Maximum encoded size of messages (where known) */
|
/* Maximum encoded size of messages (where known) */
|
||||||
/* meshtastic_NodeDatabase_size depends on runtime parameters */
|
/* meshtastic_NodeDatabase_size depends on runtime parameters */
|
||||||
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
|
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
|
||||||
#define meshtastic_BackupPreferences_size 2271
|
#define meshtastic_BackupPreferences_size 2273
|
||||||
#define meshtastic_ChannelFile_size 718
|
#define meshtastic_ChannelFile_size 718
|
||||||
#define meshtastic_DeviceState_size 1737
|
#define meshtastic_DeviceState_size 1737
|
||||||
#define meshtastic_NodeInfoLite_size 196
|
#define meshtastic_NodeInfoLite_size 196
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
|||||||
/* Maximum encoded size of messages (where known) */
|
/* Maximum encoded size of messages (where known) */
|
||||||
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
|
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
|
||||||
#define meshtastic_LocalConfig_size 747
|
#define meshtastic_LocalConfig_size 747
|
||||||
#define meshtastic_LocalModuleConfig_size 669
|
#define meshtastic_LocalModuleConfig_size 671
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} /* extern "C" */
|
} /* extern "C" */
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user