Compare commits

..

93 Commits

Author SHA1 Message Date
mverch67
6b05f090dc reduce PSRAM allocated to lvgl 2025-09-18 10:17:58 +02:00
Manuel
e9b87f7a81 Merge branch 'master' into arduino-esp32-v3.2 2025-09-18 09:48:51 +02:00
Ben Meadors
173b75a1c0 Merge pull request #7526 from DaneEvans/ci/merge-queue 2025-09-17 19:07:00 -05:00
Ben Meadors
b0dae54c97 Merge branch 'master' into ci/merge-queue 2025-09-17 19:06:24 -05:00
Thomas Göttgens
71d84404c6 add WIP for Unit C6L (#7433)
* add WIP for Unit C6L
* adapt to new config structure
* Add c6l BLE and screen support (#7991)
* Minor c6l fix
* Move out of PRIVATE_HW
---------
Co-authored-by: Austin <vidplace7@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Jason P <Xaositek@users.noreply.github.com>
Co-authored-by: Markus <Links2004@users.noreply.github.com>
2025-09-17 22:40:55 +02:00
github-actions[bot]
6f5bdd73cb Upgrade trunk (#7868)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-09-17 06:09:18 -05:00
github-actions[bot]
cc3c568501 Update protobufs (#8005)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-09-16 07:20:44 -05:00
renovate[bot]
13ebceb3bc Update meshtastic/device-ui digest to 9ed5355 (#7987)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 06:42:08 -05:00
Ben Meadors
70724bef72 Fix overflow of time value (#7984)
* Fix overflow of time value

* Revert "Fix overflow of time value"

This reverts commit 0847969201.

* That got boogered up
2025-09-14 08:12:38 -05:00
Mike Robbins
bf4e2e8e86 Fix GPS gm_mktime memory leak (#7981) 2025-09-14 06:36:16 -05:00
Ben Meadors
2dc7760508 Scale probe buffer size based on current baud rate (#7975)
* Scale probe buffer size based on current baud rate

* Throttle bad time validation logging and fix time comparison logic

* Remove comment

* Missed the other instances

* Copy pasta
2025-09-14 06:31:17 -05:00
Tom Fifield
d201f6a1ed Guard bad time warning logs using GPS_DEBUG (#7897)
In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy.

In combination, these result in a spamming of the logs when a bad time is found

When the GPS is active, we're calling the GPS thread every 0.2secs.

So this log could be printed 4,500 times in a no-lock scenario :)

Reserve this experience for developers using GPS_DEBUG.

Fixes https://github.com/meshtastic/firmware/issues/7896
2025-09-14 05:00:42 -05:00
Ben Meadors
9977035499 Fix DRAM overflow on old esp32 targets 2025-09-13 20:14:10 -05:00
Ben Meadors
096afa07f8 Tweak maximums 2025-09-13 18:57:00 -05:00
Ben Meadors
760471d620 Fix json report crashes on esp32 (#7978) 2025-09-13 18:52:46 -05:00
renovate[bot]
6165b4f7a9 Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (#7977)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 16:31:56 -05:00
Ben Meadors
ae814b5463 Drop the limit 2025-09-13 12:07:14 -05:00
Ben Meadors
4ee07226e4 Missed 2025-09-13 11:59:58 -05:00
Ben Meadors
78dfb05eeb Portduino dynamic alloc 2025-09-13 11:59:50 -05:00
Ben Meadors
9211b1bb4b Static memory pool allocation (#7966)
* Static memory pool

* Initializer

* T-Lora Pager: Support LR1121 and SX1280 models (#7956)

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs

---------

Co-authored-by: WillyJL <me@willyjl.dev>
2025-09-13 07:01:07 -05:00
Ben Meadors
70ac3601b0 Trunk 2025-09-13 06:57:12 -05:00
Ben Meadors
51acd92a37 Trunk 2025-09-13 06:51:18 -05:00
WillyJL
6d2093650a T-Lora Pager: Support LR1121 and SX1280 models (#7956)
* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs
2025-09-13 06:50:53 -05:00
github-actions[bot]
b6dd99917d Update protobufs (#7973)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-09-13 06:37:58 -05:00
Ben Meadors
d00b2afe1d Merge pull request #7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak
Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap
2025-09-12 18:30:28 -05:00
Ben Meadors
e49b07ac8c Merge pull request #7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak
Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap
2025-09-12 18:30:01 -05:00
Ben Meadors
8989de118c Only queue 2 client notification 2025-09-12 16:07:27 -05:00
Ben Meadors
1914fa0321 Formatting 2025-09-12 15:49:56 -05:00
Manuel
d47d249550 Merge branch 'master' into arduino-esp32-v3.2 2025-09-12 21:15:43 +02:00
Mike Robbins
962e5d513c Fix memory leak in NextHopRouter: always free packet copy when removing from pending 2025-09-12 13:16:48 -05:00
Ben Meadors
ac4bcd2f56 Cleanup 2025-09-11 18:57:30 -05:00
Ben Meadors
e17c50bb86 Put guards in place around debug heap operations (#7955)
* Put guards in place around debug heap operations

* Add macros to clean up code

* Add pointer as well
2025-09-11 07:57:42 -05:00
Tom Fifield
abc0eb196a Fix build error in rak_wismesh_tap_v2 (#7905)
In the logs was:
"No screen resolution defined in build_flags. Please define DISPLAY_SIZE."

set according to similar devices.
2025-09-10 16:28:49 -05:00
Ben Meadors
701028b749 Unify build epoch to add flag in platformio-custom.py (#7917)
* Unify build_epoch replacement logic in platformio-custom

* Missed one
2025-09-11 06:29:50 +10:00
Ben Meadors
108bdf7b0d Close should set heartbeatReceived = false 2025-09-09 19:11:39 -05:00
Ben Meadors
f267b5f5f7 Exclude trackball if we aren't a trackball device 2025-09-09 11:15:55 -05:00
Ben Meadors
0cd860e300 RangeTest must be enabled 2025-09-09 10:53:18 -05:00
Ben Meadors
31fdb36987 Detection sensor add module only when enabled 2025-09-09 10:46:33 -05:00
Jonathan Bennett
e7741c20e4 Add LOG_HEAP log type, and more heap debug messages (#7937) 2025-09-09 10:29:07 -05:00
Ben Meadors
d1d16fc25f Make phone queues use a static pointer queue (#7919)
* Make phone queues use a static pointer queue

* Static init

* Compile time constants now

* Instead, lets just use the normal pointerqueue for linux native builds and static for IoT platforms

* Add missing method

* Missing methods

* Update variant.h
2025-09-09 08:21:46 -05:00
Ben Meadors
c8afbe68b5 Use char buffer for probeResponse (#7870)
* Use char buffer for probeResponse

* \Update src/gps/GPS.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Revert "\Update src/gps/GPS.cpp"

This reverts commit 54d64e19f7.

* Remove string

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-09 06:34:38 -05:00
Ben Meadors
803e96800e ATAK module should be disabled for non-TAK roles (#7928) 2025-09-08 17:21:55 -05:00
renovate[bot]
6c69780615 chore(deps): update meshtastic/device-ui digest to 3677476 (#7925)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 16:52:21 -05:00
Manuel
ff28355e16 Merge branch 'master' into arduino-esp32-v3.2 2025-09-08 19:20:15 +02:00
Tom Fifield
d5bb566276 Only log good times. (It's not always a good time then) (#7904)
Further to https://github.com/meshtastic/firmware/pull/7897 ,
there was another log line that was triggering indiscriminantly on
GPS_INTERVAL_THRESHOLD .

Rather than logging a bad time 4000 times, let's just log one good time
when it is set.
2025-09-08 05:59:37 -05:00
Manuel
39ff880506 reorganize 8MB partition for MUI devices (#7860)
* reorganize 8MB partition for MUI devices

* update device-install scripts to MUI 8MB partition scheme
2025-09-08 05:56:47 -05:00
renovate[bot]
209157c9dd chore(deps): update meshtastic/device-ui digest to 233d18e (#7890)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 05:55:44 -05:00
Manuel
fb59d68edd fix uninitialized kbchar (#7889) 2025-09-08 05:45:11 -05:00
Manuel
351cbd723b Merge branch 'master' into arduino-esp32-v3.2 2025-09-08 10:41:14 +02:00
github-actions[bot]
7b854fb5ca Update protobufs (#7903)
Co-authored-by: fifieldt <1287116+fifieldt@users.noreply.github.com>
2025-09-08 11:12:52 +10:00
Tom Fifield
f8b160595f Fix merge conflict with test changes (#7902)
289f90bdbe

merged a commit that relied on

5b9db81819

but the latter commit was not merged.

This does manual wrangling to make sure the same file that exists on develop
right now ends up on master.
2025-09-08 11:02:29 +10:00
Dane Evans
18d005d7e6 explicit ignores 2025-08-24 08:12:40 +10:00
Dane Evans
8791cd7851 touching to check grandfathering 2025-08-24 08:12:40 +10:00
Dane Evans
590db89643 lint etc 2025-08-24 08:12:40 +10:00
Dane Evans
ea1d968777 update comment 2025-08-24 08:12:40 +10:00
Dane Evans
40d728a14b kerning in yaml. 2025-08-24 08:12:40 +10:00
Dane Evans
e39b56547e try vars 2025-08-24 08:12:40 +10:00
Dane Evans
a7f63d5783 add merge queue 2025-08-24 08:12:40 +10:00
Manuel
be1a724ab4 Merge branch 'master' into arduino-esp32-v3.2 2025-08-10 00:11:27 +02:00
mverch67
04f98cf20e update config; include wifi 2025-08-09 23:49:16 +02:00
mverch67
75a1a41d8c fix duplicate Syslog classes 2025-08-09 23:43:45 +02:00
mverch67
344efa6675 update device-ui (fix native compilation) 2025-08-08 17:29:02 +02:00
mverch67
e919b3da9c cleanup 2025-08-06 10:36:04 +02:00
Manuel
155783c782 Merge branch 'master' into arduino-esp32-v3.2 2025-08-06 09:15:44 +02:00
Manuel
76bcd05259 Merge branch 'master' into arduino-esp32-v3.2 2025-08-06 00:09:37 +02:00
mverch67
2d29cbc34c device-ui: use fast mem for critical draw functions 2025-08-06 00:08:54 +02:00
mverch67
2b77864192 fix attachInterrupt issue 2025-08-05 23:59:37 +02:00
mverch67
10aa4cbfee don't exclude I2C 2025-08-02 19:07:34 +02:00
mverch67
c24a174490 don't exclude I2C 2025-08-02 19:05:07 +02:00
Manuel
3e5e19efec Merge branch 'master' into arduino-esp32-v3.2 2025-08-01 21:50:40 +02:00
mverch67
ea2c247024 pr: update device-ui for indicator 2025-08-01 21:45:40 +02:00
mverch67
0b463b6443 update references to fixed io expander 2025-08-01 20:54:57 +02:00
mverch67
7d3c804279 update wdt task 2025-08-01 20:54:15 +02:00
mverch67
1fa7d9f40e try-fix watchdog trigger 2025-08-01 09:23:01 +02:00
mverch67
aa7abb1d3e use ng-io-expander 3.3.0 2025-07-30 23:37:09 +02:00
mverch67
effb454af0 comment out assert on watchdog creation 2025-07-30 23:24:31 +02:00
mverch67
27bb42ed3b git ignore new toolchain tmp files 2025-07-29 22:08:07 +02:00
mverch67
05dc8caed8 platformio updates 2025-07-29 22:07:18 +02:00
mverch67
f3734d407d remove OTA partition for now until fw size got smaller again 2025-07-29 22:06:21 +02:00
mverch67
bc8e509a8c fix copy&paste 2025-07-29 22:05:29 +02:00
mverch67
74d0472834 fix merge again 2025-07-29 21:55:34 +02:00
mverch67
5afc43ebc1 try-out WDT timer issue --> needs revert 2025-07-29 21:07:28 +02:00
mverch67
6cdb92b7a8 fix merge error 2025-07-29 21:06:19 +02:00
Thomas Göttgens
0e1872398a Merge remote-tracking branch 'remotes/origin/master' into arduino-esp32-v3.2
# Conflicts:
#	arch/esp32/esp32.ini
#	src/graphics/draw/MenuHandler.cpp
#	src/modules/Modules.cpp
#	variants/esp32s3/seeed-sensecap-indicator/platformio.ini
2025-07-29 13:06:13 +02:00
mverch67
f25544c7e0 updated platform references 2025-07-18 03:16:54 +02:00
mverch67
f40b2ba153 temp workaround for nimble compile 2025-07-18 03:14:45 +02:00
mverch67
5033fd1f9f fix MESHTASTIC_EXCLUDE_I2C compile errors 2025-07-17 12:40:02 +02:00
mverch67
cd170fb011 boot_freq 120MHz 2025-07-16 20:50:25 +02:00
mverch67
fa169a5e43 compare IDF versions 2025-07-14 17:14:21 +02:00
mverch67
c6e2a53a02 exclude not needed modules 2025-07-03 17:08:29 +02:00
Ben Meadors
a286c06271 Merge branch 'master' into arduino-esp32-v3.2 2025-07-02 06:00:44 -05:00
Manuel
dda4b90e34 Merge branch 'master' into arduino-esp32-v3.2 2025-07-01 00:56:56 +02:00
mverch67
cc5dc5046c indicator: adaptations for arduino-esp32 v3.2 2025-07-01 00:48:11 +02:00
84 changed files with 2304 additions and 337 deletions

View File

@@ -11,11 +11,6 @@ runs:
ref: ${{github.event.pull_request.head.ref}}
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
shell: bash
run: |

View File

@@ -3,7 +3,7 @@ concurrency:
group: ci-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
on:
# # Triggers the workflow on push but only for the master branch
# # Triggers the workflow on push but only for the main branches
push:
branches:
- master
@@ -258,6 +258,7 @@ jobs:
push: false
gather-artifacts:
# trunk-ignore(checkov/CKV2_GHA_1)
permissions:
contents: write
pull-requests: write

508
.github/workflows/merge_queue.yml vendored Normal file
View 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

9
.gitignore vendored
View File

@@ -39,5 +39,14 @@ release/
src/mesh/raspihttp/certificate.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)
data/boot/logo.*

View File

@@ -8,15 +8,15 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.469
- renovate@41.94.0
- checkov@3.2.471
- renovate@41.115.2
- prettier@3.6.2
- trufflehog@3.90.5
- trufflehog@3.90.6
- yamllint@1.37.1
- bandit@1.8.6
- trivy@0.66.0
- taplo@0.10.0
- ruff@0.12.11
- ruff@0.13.0
- isort@6.0.1
- markdownlint@0.45.0
- oxipng@9.1.5

6
8MB_no_ota.csv Normal file
View 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,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x660000
5 spiffs data spiffs 0x670000 0x180000
6 coredump data coredump 0x7F0000 0x10000

View File

@@ -52,8 +52,8 @@ lib_deps =
https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
h2zero/NimBLE-Arduino@^1.4.3
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/mverch67/libpax gitBranch=master
https://github.com/mverch67/libpax/archive/6f52ee989301cdabaeef00bcbf93bff55708ce2f.zip
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
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

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env bash
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
export PIP_BREAK_SYSTEM_PACKAGES=1
if (echo $2 | grep -q "esp32"); then

View File

@@ -7,6 +7,7 @@ SET "DEBUG=0"
SET "PYTHON="
SET "TFT_BUILD=0"
SET "BIGDB8=0"
SET "MUIDB8=0"
SET "BIGDB16=0"
SET "ESPTOOL_BAUD=115200"
SET "ESPTOOL_CMD="
@@ -17,7 +18,8 @@ SET "BPS_RESET=0"
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"
@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 "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
@@ -162,6 +164,15 @@ FOR %%a IN (%BIGDB_8MB%) DO (
)
: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 (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %BIGDB_16MB%.
@@ -172,6 +183,7 @@ FOR %%a IN (%BIGDB_16MB%) DO (
:end_loop_bigdb_16mb
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."
@REM Extract BASENAME from %FILENAME% for later use.
@@ -216,6 +228,12 @@ IF %BIGDB8% EQU 1 (
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.
IF %BIGDB16% EQU 1 (
SET "OTA_OFFSET=0x650000"
@@ -232,14 +250,14 @@ IF NOT EXIST !SPIFFS_FILENAME! CALL :LOG_MESSAGE ERROR "File does not exist: "!S
@REM Flashing operations.
CALL :LOG_MESSAGE INFO "Trying to flash "!FILENAME!", but first erasing and writing system information..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! erase-flash || GOTO eof
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash 0x00 "!FILENAME!" || GOTO eof
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! erase_flash || GOTO eof
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x00 "!FILENAME!" || GOTO eof
CALL :LOG_MESSAGE INFO "Trying to flash BLEOTA "!OTA_FILENAME!" at OTA_OFFSET !OTA_OFFSET!..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !OTA_OFFSET! "!OTA_FILENAME!" || GOTO eof
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash !OTA_OFFSET! "!OTA_FILENAME!" || GOTO eof
CALL :LOG_MESSAGE INFO "Trying to flash SPIFFS "!SPIFFS_FILENAME!" at SPIFFS_OFFSET !SPIFFS_OFFSET!..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !SPIFFS_OFFSET! "!SPIFFS_FILENAME!" || GOTO eof
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash !SPIFFS_OFFSET! "!SPIFFS_FILENAME!" || GOTO eof
CALL :LOG_MESSAGE INFO "Script complete!."
@@ -251,9 +269,9 @@ EXIT /B %ERRORLEVEL%
:RUN_ESPTOOL
@REM Subroutine used to run ESPTOOL_CMD with arguments.
@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 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"
CALL :RESET_ERROR
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4

View File

@@ -11,31 +11,33 @@ FIRMWARE_OFFSET=0x00
# Variant groups
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"
"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"
)
MUIDB_8MB=(
"picomputer-s3"
"unphone"
"seeed-sensecap-indicator"
)
BIGDB_16MB=(
"t-deck"
"mesh-tab"
"t-energy-s3"
"dreamcatcher"
"ESP32-S3-Pico"
"m5stack-cores3"
"station-g2"
"t-deck"
"mesh-tab"
"t-energy-s3"
"dreamcatcher"
"ESP32-S3-Pico"
"m5stack-cores3"
"station-g2"
"t-eth-elite"
"tlora-pager"
"t-watch-s3"
@@ -110,8 +112,8 @@ while [ $# -gt 0 ]; do
shift
;;
--1200bps-reset)
BPS_RESET=true
;;
BPS_RESET=true
;;
--) # Stop parsing options
shift
break
@@ -162,6 +164,13 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
fi
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.
for variant in "${BIGDB_16MB[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
@@ -208,9 +217,9 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
$ESPTOOL_CMD erase-flash
$ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
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}"
$ESPTOOL_CMD write-flash $OFFSET "${SPIFFSFILE}"
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
else
show_help

View File

@@ -6,6 +6,8 @@ from os.path import join
import subprocess
import json
import re
import time
from datetime import datetime
from readprops import readProps
@@ -125,11 +127,16 @@ for pref in userPrefs:
pref_flags.append("-D" + pref + "=" + env.StringifyMacro(userPrefs[pref]) + "")
# 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 = [
"-DAPP_VERSION=" + verObj["long"],
"-DAPP_VERSION_SHORT=" + verObj["short"],
"-DAPP_ENV=" + env.get("PIOENV"),
"-DAPP_REPO=" + repo_owner,
"-DBUILD_EPOCH=" + str(build_epoch),
] + pref_flags
print ("Using flags:")

View File

@@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv",
"partitions": "partition-table-8MB.csv",
"memory_type": "qio_opi"
},
"core": "esp32",
@@ -16,6 +16,7 @@
"f_cpu": "240000000L",
"f_flash": "80000000L",
"f_boot": "120000000L",
"boot_freq": "120000000L",
"boot": "qio",
"flash_mode": "qio",
"psram_type": "opi",

View File

@@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi",
"partitions": "default_8MB.csv"
"partitions": "partition-table-8MB.csv"
},
"core": "esp32",
"extra_flags": [

7
partition-table-8MB.csv Normal file
View 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
1 # This is a layout for 8MB of flash for MUI devices
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x5000,
4 otadata, data, ota, 0xe000, 0x2000,
5 app0, app, ota_0, 0x10000, 0x5C0000,
6 flashApp, app, ota_1, 0x5D0000,0x0A0000,
7 spiffs, data, spiffs, 0x670000,0x180000

View File

@@ -53,14 +53,14 @@ build_flags = -Wno-missing-field-initializers
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
-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
monitor_speed = 115200
monitor_filters = direct
lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/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
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
@@ -87,8 +87,9 @@ check_flags =
framework = arduino
lib_deps =
${env.lib_deps}
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
end2endzone/NonBlockingRTTTL@1.3.0
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=mverch67/library/NonBlockingRTTTL
https://github.com/mverch67/NonBlockingRTTTL/archive/ad1c2fb12bc81db546c6a94e963acb3382d3689e.zip ; TODO
build_flags = ${env.build_flags} -Os
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
@@ -99,8 +100,6 @@ lib_deps =
thingsboard/TBPubSubClient@2.12.1
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
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)
[nrf52_networking_base]
@@ -118,7 +117,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/a04bc94b45dacdabf3ae1832d4591390e35fc61f.zip
https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]

View File

@@ -183,9 +183,9 @@ class AmbientLightingThread : public concurrency::OSThread
#endif
#endif
pixels.show();
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.green, moduleConfig.ambient_lighting.blue);
// 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.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);

View File

@@ -2,6 +2,12 @@
#include "configuration.h"
// Forward declarations
#if defined(DEBUG_HEAP)
class MemGet;
extern MemGet memGet;
#endif
// DEBUG LED
#ifndef LED_STATE_ON
#define LED_STATE_ON 1
@@ -23,6 +29,7 @@
#define MESHTASTIC_LOG_LEVEL_ERROR "ERROR"
#define MESHTASTIC_LOG_LEVEL_CRIT "CRIT "
#define MESHTASTIC_LOG_LEVEL_TRACE "TRACE"
#define MESHTASTIC_LOG_LEVEL_HEAP "HEAP"
#include "SerialConsole.h"
@@ -62,6 +69,25 @@
#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
extern "C" void logLegacy(const char *level, const char *fmt, ...);

View File

@@ -851,9 +851,9 @@ void Power::readPowerStatus()
running++;
}
}
LOG_DEBUG(threadlist);
LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
LOG_HEAP(threadlist);
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));
lastheap = memGet.getFreeHeap();
}
#ifdef DEBUG_HEAP_MQTT

View File

@@ -86,9 +86,9 @@ void OSThread::run()
#ifdef DEBUG_HEAP
auto newHeap = memGet.getFreeHeap();
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)
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
runned();

View File

@@ -294,6 +294,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = AHT10;
break;
#endif
#if !defined(M5STACK_UNITC6L)
case INA_ADDR:
case INA_ADDR_ALTERNATE:
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
}
break;
#endif
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
// weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips.

View File

@@ -1,5 +1,4 @@
#include <cstring> // Include for strstr
#include <string>
#include <vector>
#include "configuration.h"
@@ -1206,7 +1205,7 @@ static const char *DETECTED_MESSAGE = "%s detected";
LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \
clearBuffer(); \
_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) { \
return detectedDriver; \
} \
@@ -1368,36 +1367,55 @@ GnssModel_t GPS::probe(int serialSpeed)
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();
while (millis() - start < timeout) {
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
LOG_DEBUG(response.c_str());
LOG_DEBUG(response);
#endif
// check if we can see our chips
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());
delete[] response; // Cleanup before return
return chipInfo.driver;
}
}
}
if (response.endsWith("\r\n")) {
response.trim();
response = ""; // Reset the response string for the next potential message
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
// Reset the response buffer for the next potential message
responseLen = 0;
response[0] = '\0';
}
}
}
#ifdef GPS_DEBUG
LOG_DEBUG(response.c_str());
LOG_DEBUG(response);
#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()
@@ -1532,10 +1550,9 @@ 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_isdst = false;
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,
t.tm_sec, ti.age());
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultSuccess) {
LOG_DEBUG("Time set.");
LOG_DEBUG("NMEA GPS time set %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour,
t.tm_min, t.tm_sec, ti.age());
return true;
} else {
return false;

View File

@@ -236,7 +236,7 @@ class GPS : private concurrency::OSThread
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
GnssModel_t probe(int serialSpeed);

View File

@@ -9,6 +9,9 @@
static RTCQuality currentQuality = RTCQualityNone;
uint32_t lastSetFromPhoneNtpOrGps = 0;
static uint32_t lastTimeValidationWarning = 0;
static const uint32_t TIME_VALIDATION_WARNING_INTERVAL_MS = 15000; // 15 seconds
RTCQuality getRTCQuality()
{
return currentQuality;
@@ -48,7 +51,9 @@ RTCSetResult readFromRTC()
#ifdef 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);
}
return RTCSetResultInvalidTime;
}
#endif
@@ -87,7 +92,10 @@ RTCSetResult readFromRTC()
#ifdef 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;
}
#endif
@@ -130,11 +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
#ifdef 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 (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
BUILD_EPOCH + FORTY_YEARS);
} 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;
}
#endif
@@ -252,11 +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
#ifdef 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 (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
BUILD_EPOCH + FORTY_YEARS);
} 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;
}
#endif
@@ -316,14 +342,40 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
time_t gm_mktime(struct tm *tm)
{
#if !MESHTASTIC_EXCLUDE_TZ
setenv("TZ", "GMT0", 1);
time_t res = mktime(tm);
if (*config.device.tzdef) {
setenv("TZ", config.device.tzdef, 1);
} else {
setenv("TZ", "UTC0", 1);
time_t result = 0;
// First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
int year = 1900 + tm->tm_year; // tm_year is years since 1900
int year_minus_one = year - 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
return mktime(tm);
#endif

View File

@@ -56,5 +56,5 @@ time_t gm_mktime(struct tm *tm);
#define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60
#ifdef BUILD_EPOCH
#define FORTY_YEARS (40UL * 365 * SEC_PER_DAY) // probably time to update your firmware
static constexpr uint64_t FORTY_YEARS = (40ULL * 365 * SEC_PER_DAY); // Use 64-bit arithmetic to prevent overflow
#endif

View File

@@ -317,6 +317,14 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(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) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
@@ -507,7 +515,7 @@ void Screen::setup()
// === Apply loaded brightness ===
#if defined(ST7789_CS)
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);
#endif
LOG_INFO("Applied screen brightness: %d", brightness);
@@ -554,7 +562,7 @@ void Screen::setup()
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
#elif defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
#else
#elif !defined(M5STACK_UNITC6L)
dispdev->flipScreenVertically();
#endif
}
@@ -692,7 +700,11 @@ int32_t Screen::runOnce()
#ifndef DISABLE_WELCOME_UNSET
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
#if defined(M5STACK_UNITC6L)
menuHandler::LoraRegionPicker();
#else
menuHandler::OnboardMessage();
#endif
}
#endif
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
@@ -890,8 +902,12 @@ void Screen::setFrames(FrameFocus focus)
#if defined(DISPLAY_CLOCK_FRAME)
fsi.positions.clock = numframes;
#if defined(M5STACK_UNITC6L)
normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
#else
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
: graphics::ClockRenderer::drawDigitalClockFrame;
#endif
indicatorIcons.push_back(digital_icon_clock);
#endif
@@ -1226,6 +1242,10 @@ void Screen::handleShowNextFrame()
void Screen::setFastFramerate()
{
#if defined(M5STACK_UNITC6L)
dispdev->clear();
dispdev->display();
#endif
// We are about to start a transition so speed up fps
targetFramerate = SCREEN_TRANSITION_FRAMERATE;
@@ -1297,13 +1317,23 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
}
} else {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
#endif
} else {
strcpy(banner, "New Message");
}
}
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
playLongBeep();
#else
screen->showSimpleBanner(banner, 3000);
#endif
}
}
@@ -1386,7 +1416,11 @@ int Screen::handleInputEvent(const InputEvent *event)
if (devicestate.rx_text_message.from) {
menuHandler::messageResponseMenu();
} else {
#if defined(M5STACK_UNITC6L)
menuHandler::textMessageMenu();
#else
menuHandler::textMessageBaseMenu();
#endif
}
} else if (framesetInfo.positions.firstFavorite != 255 &&
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&

View File

@@ -81,6 +81,8 @@ class Screen
#include <SSD1306Wire.h>
#elif defined(USE_ST7789)
#include <ST7789Spi.h>
#elif defined(USE_SPISSD1306)
#include <SSD1306Spi.h>
#else
// the SH1106/SSD1306 variant is auto-detected
#include <AutoOLEDWire.h>

View File

@@ -79,6 +79,10 @@
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
#define FONT_MEDIUM 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
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
#define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19

View File

@@ -124,7 +124,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1;
#if !defined(M5STACK_UNITC6L)
// === Battery Icons ===
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
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
}

View File

@@ -277,12 +277,13 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
// Line 1 (Still)
#if !defined(M5STACK_UNITC6L)
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
if (config.display.heading_bold)
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
display->setColor(WHITE);
#endif
// Setup string to assemble analogClock string
std::string analogClock = "";
@@ -386,17 +387,24 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
char shortnameble[35];
getMacAddr(dmac);
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);
#endif
int textWidth = display->getStringWidth(shortnameble);
int nameX = (SCREEN_WIDTH - textWidth);
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
// === Second Row: Radio Preset ===
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
char regionradiopreset[25];
const char *region = myRegion ? myRegion->name : NULL;
if (region != nullptr) {
#if defined(M5STACK_UNITC6L)
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region);
#else
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
#endif
}
textWidth = display->getStringWidth(regionradiopreset);
nameX = (SCREEN_WIDTH - textWidth) / 2;
@@ -408,9 +416,17 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
float freq = RadioLibInterface::instance->getFreq();
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
if (config.lora.channel_num == 0) {
#if defined(M5STACK_UNITC6L)
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz", freqStr);
#else
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
#endif
} 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);
#endif
}
size_t len = strlen(frequencyslot);
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
@@ -420,6 +436,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
#if !defined(M5STACK_UNITC6L)
// === Fourth Row: Channel Utilization ===
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
@@ -476,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],
chUtilPercentage);
#endif
}
// ****************************
@@ -501,8 +519,11 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
#ifdef USE_EINK
barsOffset -= 12;
#endif
#if defined(M5STACK_UNITC6L)
const int barX = x + 45 + barsOffset;
#else
const int barX = x + 40 + barsOffset;
#endif
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
if (total == 0)
return;
@@ -527,7 +548,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
// Label
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(labelX, getTextPositions(display)[line], label);
#if !defined(M5STACK_UNITC6L)
// Bar
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
display->setColor(WHITE);
@@ -535,7 +556,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->fillRect(barX, barY, fillWidth, barHeight);
display->setColor(WHITE);
#endif
// Value string
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
@@ -588,10 +609,16 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
line += 1;
}
line += 1;
char appversionstr[35];
snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION));
char appversionstr_formatted[40];
char *lastDot = strrchr(appversionstr, '.');
#if defined(M5STACK_UNITC6L)
if (lastDot != nullptr) {
*lastDot = '\0'; // truncate string
}
#else
if (lastDot) {
size_t prefixLen = lastDot - appversionstr;
strncpy(appversionstr_formatted, appversionstr, prefixLen);
@@ -602,10 +629,12 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
appversionstr[sizeof(appversionstr) - 1] = '\0';
}
#endif
int textWidth = display->getStringWidth(appversionstr);
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
line += 1;
char uptimeStr[32] = "";
@@ -624,6 +653,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
}
#endif
}
} // namespace DebugRenderer
} // namespace graphics

View File

@@ -79,7 +79,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
"NP_865",
"BR_902"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "LoRa Region";
#else
bannerOptions.message = "Set the LoRa region";
#endif
bannerOptions.durationMs = duration;
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 27;
@@ -260,7 +264,11 @@ void menuHandler::TZPicker()
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"};
#endif
enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Clock Action";
@@ -284,8 +292,11 @@ void menuHandler::clockMenu()
void menuHandler::messageResponseMenu()
{
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"};
#endif
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
int options = 3;
@@ -299,7 +310,11 @@ void menuHandler::messageResponseMenu()
optionsEnumArray[options++] = Aloud;
#endif
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Message";
#else
bannerOptions.message = "Message Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -349,7 +364,11 @@ void menuHandler::homeBaseMenu()
optionsArray[options] = "Send Position";
optionsEnumArray[options++] = Position;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "New Preset";
#else
optionsArray[options] = "New Preset Msg";
#endif
optionsEnumArray[options++] = Preset;
if (kb_found) {
optionsArray[options] = "New Freetext Msg";
@@ -357,7 +376,11 @@ void menuHandler::homeBaseMenu()
}
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Home";
#else
bannerOptions.message = "Home Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -396,6 +419,11 @@ void menuHandler::homeBaseMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::textMessageMenu()
{
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
}
void menuHandler::textMessageBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, enumEnd };
@@ -439,11 +467,17 @@ void menuHandler::systemBaseMenu()
optionsArray[options] = "Screen Options";
optionsEnumArray[options++] = ScreenOptions;
#endif
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Bluetooth";
#else
optionsArray[options] = "Bluetooth Toggle";
#endif
optionsEnumArray[options++] = Bluetooth;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Power";
#else
optionsArray[options] = "Reboot/Shutdown";
#endif
optionsEnumArray[options++] = PowerMenu;
if (test_enabled) {
@@ -452,7 +486,11 @@ void menuHandler::systemBaseMenu()
}
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "System";
#else
bannerOptions.message = "System Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -485,7 +523,11 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu()
{
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"};
#endif
static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2;
@@ -493,13 +535,19 @@ void menuHandler::favoriteBaseMenu()
optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext;
}
#if !defined(M5STACK_UNITC6L)
optionsArray[options] = "Trace Route";
optionsEnumArray[options++] = TraceRoute;
#endif
optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Favorites";
#else
bannerOptions.message = "Favorites Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -528,10 +576,12 @@ void menuHandler::positionBaseMenu()
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
int options = 3;
#if !MESHTASTIC_EXCLUDE_I2C
if (accelerometerThread) {
optionsArray[options] = "Compass Calibrate";
optionsEnumArray[options++] = CompassCalibrate;
}
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -544,8 +594,10 @@ void menuHandler::positionBaseMenu()
} else if (selected == CompassMenu) {
menuQueue = compass_point_north_menu;
screen->runNow();
#if !MESHTASTIC_EXCLUDE_I2C
} else if (selected == CompassCalibrate) {
accelerometerThread->calibrate(30);
#endif
}
};
screen->showOverlayBanner(bannerOptions);
@@ -554,11 +606,19 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu()
{
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"};
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray;
#if defined(M5STACK_UNITC6L)
bannerOptions.optionsCount = 3;
#else
bannerOptions.optionsCount = 5;
#endif
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Favorite) {
menuQueue = add_favorite;
@@ -665,7 +725,11 @@ void menuHandler::BluetoothToggleMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Bluetooth";
#else
bannerOptions.message = "Toggle Bluetooth";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -857,7 +921,11 @@ void menuHandler::rebootMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Reboot";
#else
bannerOptions.message = "Reboot Device?";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -877,7 +945,11 @@ void menuHandler::shutdownMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Shutdown";
#else
bannerOptions.message = "Shutdown Device?";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -894,7 +966,12 @@ void menuHandler::shutdownMenu()
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 {
#endif
LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
@@ -1090,7 +1167,11 @@ void menuHandler::powerMenu()
#endif
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Power";
#else
bannerOptions.message = "Reboot / Shutdown";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;

View File

@@ -76,6 +76,7 @@ class menuHandler
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
static void textMessageMenu();
private:
static void saveUIConfig();

View File

@@ -181,12 +181,19 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
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 scrollBottom = SCREEN_HEIGHT - navHeight;
const int usableHeight = scrollBottom;
const int textWidth = SCREEN_WIDTH;
#endif
bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
bool isBold = config.display.heading_bold;
@@ -201,7 +208,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
graphics::drawCommonHeader(display, x, y, titleStr);
const char *messageString = "No messages";
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);
#endif
return;
}
@@ -209,6 +220,10 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
char headerStr[80];
const char *sender = "???";
#if defined(M5STACK_UNITC6L)
if (node && node->has_user)
sender = node->user.short_name;
#else
if (node && node->has_user) {
if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) {
sender = node->user.long_name;
@@ -216,6 +231,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
sender = node->user.short_name;
}
}
#endif
uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
uint8_t timestampHours, timestampMinutes;
int32_t daysAgo;
@@ -235,10 +251,61 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
sender);
}
} 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(),
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();
#ifndef EXCLUDE_EMOJI
// === Bounce animation setup ===
@@ -355,6 +422,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Draw header at the end to sort out overlapping elements
graphics::drawCommonHeader(display, x, y, titleStr);
#endif
}
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)

View File

@@ -21,6 +21,10 @@ extern bool haveGlyphs(const char *str);
// Global screen instance
extern graphics::Screen *screen;
#if defined(M5STACK_UNITC6L)
static uint32_t lastSwitchTime = 0;
#else
#endif
namespace graphics
{
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 rowYOffset = FONT_HEIGHT_SMALL - 3;
#if defined(M5STACK_UNITC6L)
int columnWidth = display->getWidth();
#else
int columnWidth = display->getWidth() / 2;
#endif
display->clear();
// Draw the battery/time header
@@ -408,8 +414,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
int visibleNodeRows = totalRowsAvailable;
#if defined(M5STACK_UNITC6L)
int totalColumns = 1;
#else
int totalColumns = 2;
#endif
int startIndex = scrollIndex * visibleNodeRows * totalColumns;
if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) {
startIndex++; // skip own node
@@ -445,12 +454,14 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
}
}
#if !defined(M5STACK_UNITC6L)
// Draw column separator
if (shownCount > 0) {
const int firstNodeY = y + 3;
drawColumnSeparator(display, x, firstNodeY, lastNodeY);
}
#endif
const int scrollStartY = y + 3;
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
}
@@ -468,6 +479,13 @@ void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state,
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)
if (lastRenderedMode == MODE_COUNT) {
currentMode = MODE_LAST_HEARD;
@@ -522,6 +540,14 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
double lat = DegD(ourNode->position.latitude_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 HAS_GPS
if (screen->hasHeading()) {

View File

@@ -459,6 +459,135 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
// count lines
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 (isHighResolution && boxWidth <= 150)
boxWidth += 26;
@@ -547,6 +676,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
}
#endif
}
/// Draw the last text message we received

View File

@@ -20,7 +20,7 @@
// External variables
extern graphics::Screen *screen;
static uint32_t lastSwitchTime = 0;
namespace graphics
{
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
@@ -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)
{
if (favoritedNodes.empty())
return;
@@ -230,8 +229,15 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
return;
uint32_t now = millis();
display->clear();
#if defined(M5STACK_UNITC6L)
if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
{
display->display();
lastSwitchTime = now;
}
#endif
currentFavoriteNodeNum = node->num;
// === Create the shortName and title string ===
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.
int line = 1; // which slot to use next
std::string usernameStr;
// === 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;
#endif
if (username) {
usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case
// 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) {
display->drawString(x, getTextPositions(display)[line++], seenStr);
}
#if !defined(M5STACK_UNITC6L)
// === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = "";
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
}
#endif
}
// ****************************
@@ -492,7 +503,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
int line = 1;
// === Header ===
#if defined(M5STACK_UNITC6L)
graphics::drawCommonHeader(display, x, y, "Home");
#else
graphics::drawCommonHeader(display, x, y, "");
#endif
// === Content below header ===
@@ -507,20 +522,25 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
config.display.heading_bold = false;
// 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");
#endif
char uptimeStr[32] = "";
uint32_t uptime = millis() / 1000;
uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
#if !defined(M5STACK_UNITC6L)
if (days)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
#endif
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
// === Second Row: Satellites and Voltage ===
@@ -549,6 +569,21 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
}
#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()) {
char batStr[20];
int batV = powerStatus->getBatteryVoltageMv() / 1000;
@@ -674,6 +709,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
}
#endif
}
// 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
// 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,
icon_width, icon_height, icon_bits);
@@ -840,7 +898,6 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
const char *title = "meshtastic.org";
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL);
// Draw region in upper left
if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg);
@@ -855,6 +912,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
screen->forceDisplay();
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);
char fullLine[40];
snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
#if !defined(M5STACK_UNITC6L)
display->drawString(0, getTextPositions(display)[line++], fullLine);
#endif
// === Third Row: Latitude ===
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);
display->drawString(x, getTextPositions(display)[line++], latStr);
#endif
// === Fourth Row: Longitude ===
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);
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());
}
display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
#endif
}
#if !defined(M5STACK_UNITC6L)
// === Draw Compass if heading is valid ===
if (validHeading) {
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
@@ -1034,6 +1104,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
}
}
#endif
#endif
}
#ifdef USERPREFS_OEM_TEXT
@@ -1190,7 +1261,6 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setColor(WHITE);
}
}
// Knock the corners off the square
display->setColor(BLACK);
display->drawRect(rectX, y - 2, 1, 1);

View File

@@ -287,6 +287,9 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
#define analog_icon_clock_height 8
const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
0b00100100, 0b01000010, 0b01000010, 0b11111111};
#ifdef M5STACK_UNITC6L
#include "img/icon_small.xbm"
#else
#include "img/icon.xbm"
#endif
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");

View 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

View File

@@ -40,10 +40,7 @@ bool RotaryEncoderImpl::init()
int32_t RotaryEncoderImpl::runOnce()
{
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
e.source = this->originName;
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()) {

95
src/input/i2cButton.cpp Normal file
View 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
View 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

View File

@@ -385,7 +385,6 @@ void setup()
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
io.pinMode(EXPANDS_SD_PULLEN, INPUT);
#endif
concurrency::hasBeenSetup = true;
#if ARCH_PORTDUINO
SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0);
@@ -544,6 +543,12 @@ void setup()
#endif
#endif
#if defined(M5STACK_UNITC6L)
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, 1);
c6l_init();
#endif
#ifdef PIN_LCD_RESET
// FIXME - move this someplace better, LCD is at address 0x3F
pinMode(PIN_LCD_RESET, OUTPUT);
@@ -877,7 +882,8 @@ void setup()
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) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_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);
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
@@ -1140,7 +1146,8 @@ void setup()
// Don't call screen setup until after nodedb is setup (because we need
// the current region name)
#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(ST7796_CS)
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_SPISSD1306)
if (screen)
screen->setup();
#elif defined(ARCH_PORTDUINO)

View File

@@ -88,4 +88,16 @@ uint32_t MemGet::getPsramSize()
#else
return 0;
#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);
}

View File

@@ -6,6 +6,7 @@
#include <memory>
#include "PointerQueue.h"
#include "configuration.h" // For LOG_WARN, LOG_DEBUG, LOG_HEAP
template <class T> class Allocator
{
@@ -14,13 +15,14 @@ template <class T> class Allocator
Allocator() : deleter([this](T *p) { this->release(p); }) {}
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
T *allocZeroed()
{
T *p = allocZeroed(0);
assert(p); // FIXME panic instead
if (!p) {
LOG_WARN("Failed to allocate zeroed memory");
}
return p;
}
@@ -39,10 +41,12 @@ template <class T> class Allocator
T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
{
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;
}
@@ -83,7 +87,11 @@ template <class T> class MemoryDynamic : public Allocator<T>
/// Return a buffer for use by others
virtual void release(T *p) override
{
assert(p);
if (p == nullptr)
return;
LOG_HEAP("Freeing 0x%x", p);
free(p);
}
@@ -96,3 +104,58 @@ template <class T> class MemoryDynamic : public Allocator<T>
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;
}
};

View File

@@ -100,7 +100,6 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
// Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
auto ourNodeNum = nodeDB->getNodeNum();
bool toUs = isBroadcast(mp.to) || isToUs(&mp);
bool fromUs = mp.from == ourNodeNum;
for (auto i = modules->begin(); i != modules->end(); ++i) {
auto &pi = **i;

View File

@@ -46,11 +46,14 @@ the new node can build its node db)
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;
@@ -61,8 +64,10 @@ Allocator<meshtastic_QueueStatus> &queueStatusPool = staticQueueStatusPool;
#include "Router.h"
MeshService::MeshService()
: toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE),
toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2)
#ifdef ARCH_PORTDUINO
: 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};
}
@@ -191,8 +196,10 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
// (so we update our nodedb for the local node)
// Send the packet into the mesh
sendToMesh(packetPool.allocCopy(p), RX_SRC_USER);
DEBUG_HEAP_BEFORE;
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)
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
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

View File

@@ -9,7 +9,12 @@
#include "MeshRadio.h"
#include "MeshTypes.h"
#include "Observer.h"
#ifdef ARCH_PORTDUINO
#include "PointerQueue.h"
#else
#include "StaticPointerQueue.h"
#endif
#include "mesh-pb-constants.h"
#if defined(ARCH_PORTDUINO)
#include "../platform/portduino/SimRadio.h"
#endif
@@ -37,16 +42,32 @@ class MeshService
/// 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
/// FIXME - save this to flash on deep sleep
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_MeshPacket> toPhoneQueue;
#else
StaticPointerQueue<meshtastic_MeshPacket, MAX_RX_TOPHONE> toPhoneQueue;
#endif
// keep list of QueueStatus packets to be send to the phone
#ifdef ARCH_PORTDUINO
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
#ifdef ARCH_PORTDUINO
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)
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_ClientNotification> toPhoneClientNotificationQueue;
#else
StaticPointerQueue<meshtastic_ClientNotification, MAX_RX_NOTIFICATION_TOPHONE> toPhoneClientNotificationQueue;
#endif
// This holds the last QueueStatus send
meshtastic_QueueStatus lastQueueStatus;

View File

@@ -175,12 +175,18 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
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);
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;
} else
return false;

View File

@@ -663,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.bluetooth.fixed_pin = defaultBLEPin;
#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(ST7796_CS)
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306)
bool hasScreen = true;
#ifdef HELTEC_MESH_NODE_T114
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);

View File

@@ -100,6 +100,7 @@ void PhoneAPI::close()
config_nonce = 0;
config_state = 0;
pauseBluetoothLogging = false;
heartbeatReceived = false;
}
}

View File

@@ -2,6 +2,7 @@
#include "Default.h"
#include "MeshTypes.h"
#include "configuration.h"
#include "memGet.h"
#include "mesh-pb-constants.h"
#include "modules/NodeInfoModule.h"
#include "modules/RoutingModule.h"
@@ -21,8 +22,10 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
if (p->hop_limit == 0) {
p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
}
DEBUG_HEAP_BEFORE;
auto copy = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("ReliableRouter::send", copy);
startRetransmission(copy, NUM_RELIABLE_RETX);
}

View File

@@ -5,6 +5,7 @@
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "configuration.h"
#include "detect/LoRaRadioType.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
// 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 \
(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<MeshPacket> staticPool(MAX_PACKETS);
static MemoryDynamic<meshtastic_MeshPacket> staticPool;
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
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;
#endif
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 (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
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);
DEBUG_HEAP_AFTER("Router::send", p_decoded);
auto encodeResult = perhapsEncode(p);
if (encodeResult != meshtastic_Routing_Error_NONE) {
@@ -607,8 +621,11 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
bool skipHandle = false;
// 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
// Store a copy of encrypted packet for MQTT
DEBUG_HEAP_BEFORE;
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
auto decodedState = perhapsDecode(p);
@@ -656,7 +673,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
// call modules here
// 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);
#if !MESHTASTIC_EXCLUDE_MQTT
@@ -670,8 +687,6 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
!isFromUs(p) && mqtt)
mqtt->onSend(*p_encrypted, *p, p->channel);
#endif
} else if (p->from == nodeDB->getNodeNum() && !skipHandle) {
MeshModule::callModules(*p, src);
}
packetPool.release(p_encrypted); // Release the encrypted packet

View 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; }
};

View File

@@ -64,7 +64,12 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
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
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;
/* Defines the device's behavior for how messages are rebroadcast */
@@ -646,8 +651,8 @@ extern "C" {
/* Helper constants for enums */
#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_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_ROUTER_LATE+1))
#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_CLIENT_BASE+1))
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY

View File

@@ -66,6 +66,8 @@ typedef enum _meshtastic_Language {
meshtastic_Language_UKRAINIAN = 16,
/* Bulgarian */
meshtastic_Language_BULGARIAN = 17,
/* Czech */
meshtastic_Language_CZECH = 18,
/* Simplified Chinese (experimental) */
meshtastic_Language_SIMPLIFIED_CHINESE = 30,
/* Traditional Chinese (experimental) */

View File

@@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_T_DECK_PRO = 102,
/* Lilygo TLora Pager */
meshtastic_HardwareModel_T_LORA_PAGER = 103,
/* GAT562 Mesh Trial Tracker */
meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
/* M5Stack Reserved */
meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
/* RAKwireless WisMesh Tag */
meshtastic_HardwareModel_WISMESH_TAG = 105,
/* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -274,6 +274,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_T_ECHO_LITE = 109,
/* New Heltec LoRA32 with ESP32-S3 CPU */
meshtastic_HardwareModel_HELTEC_V4 = 110,
/* M5Stack C6L */
meshtastic_HardwareModel_M5STACK_C6L = 111,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */

View File

@@ -292,11 +292,14 @@ JSONArray htmlListDir(const char *dirname, uint8_t levels)
JSONObject thisFileMap;
thisFileMap["size"] = new JSONValue((int)file.size());
#ifdef ARCH_ESP32
thisFileMap["name"] = new JSONValue(String(file.path()).substring(1).c_str());
String fileName = String(file.path()).substring(1);
thisFileMap["name"] = new JSONValue(fileName.c_str());
#else
thisFileMap["name"] = new JSONValue(String(file.name()).substring(1).c_str());
String fileName = String(file.name()).substring(1);
thisFileMap["name"] = new JSONValue(fileName.c_str());
#endif
if (String(file.name()).substring(1).endsWith(".gz")) {
String tempName = String(file.name()).substring(1);
if (tempName.endsWith(".gz")) {
#ifdef ARCH_ESP32
String modifiedFile = String(file.path()).substring(1);
#else
@@ -339,7 +342,8 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
@@ -367,7 +371,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
return;
} else {
@@ -376,7 +381,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("Error");
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
return;
}
@@ -622,10 +628,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
tempArray.push_back(new JSONValue((int)logArray[i]));
}
JSONValue *result = new JSONValue(tempArray);
// Clean up original array to prevent memory leak
for (auto *val : tempArray) {
delete val;
}
// Note: Don't delete tempArray elements here - JSONValue now owns them
return result;
};
@@ -656,7 +659,9 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
// data->wifi
JSONObject jsonObjWifi;
jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI());
jsonObjWifi["ip"] = new JSONValue(WiFi.localIP().toString().c_str());
String wifiIPString = WiFi.localIP().toString();
std::string wifiIP = wifiIPString.c_str();
jsonObjWifi["ip"] = new JSONValue(wifiIP.c_str());
// data->memory
JSONObject jsonObjMemory;
@@ -702,7 +707,8 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
}
@@ -773,7 +779,8 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
// Clean up the nodesArray to prevent memory leak
@@ -926,7 +933,8 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
}
@@ -968,7 +976,8 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
// Clean up the networkObjs to prevent memory leak

View File

@@ -15,8 +15,27 @@
// FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in
// RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
#ifndef MAX_RX_TOPHONE
#if defined(ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3))
#define MAX_RX_TOPHONE 8
#else
#define MAX_RX_TOPHONE 32
#endif
#endif
/// max number of QueueStatus packets which can be waiting for delivery to phone
#ifndef MAX_RX_QUEUESTATUS_TOPHONE
#define MAX_RX_QUEUESTATUS_TOPHONE 2
#endif
/// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone
#ifndef MAX_RX_MQTTPROXY_TOPHONE
#define MAX_RX_MQTTPROXY_TOPHONE 8
#endif
/// max number of ClientNotification packets which can be waiting for delivery to phone
#ifndef MAX_RX_NOTIFICATION_TOPHONE
#define MAX_RX_NOTIFICATION_TOPHONE 2
#endif
/// Verify baseline assumption of node size. If it increases, we need to reevaluate
/// the impact of its memory footprint, notably on MAX_NUM_NODES.

View File

@@ -1432,10 +1432,17 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node;
if (node) {
if (node->is_favorite) {
#if defined(M5STACK_UNITC6L)
snprintf(entryText, sizeof(entryText), "* %s", node->user.short_name);
} else {
snprintf(entryText, sizeof(entryText), "%s", node->user.short_name);
}
#else
snprintf(entryText, sizeof(entryText), "* %s", node->user.long_name);
} else {
snprintf(entryText, sizeof(entryText), "%s", node->user.long_name);
}
#endif
}
}
}
@@ -1606,7 +1613,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
int yOffset = y + 10;
#else
display->setFont(FONT_MEDIUM);
#if defined(M5STACK_UNITC6L)
int yOffset = y;
#else
int yOffset = y + 10;
#endif
#endif
// --- Delivery Status Message ---
@@ -1631,13 +1642,20 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
}
display->drawString(display->getWidth() / 2 + x, yOffset, buffer);
#if defined(M5STACK_UNITC6L)
yOffset += lineCount * FONT_HEIGHT_MEDIUM - 5; // only 1 line gap, no extra padding
#else
yOffset += lineCount * FONT_HEIGHT_MEDIUM; // only 1 line gap, no extra padding
#endif
#ifndef USE_EINK
// --- SNR + RSSI Compact Line ---
if (this->ack) {
display->setFont(FONT_SMALL);
#if defined(M5STACK_UNITC6L)
snprintf(buffer, sizeof(buffer), "SNR: %.1f dB \nRSSI: %d", this->lastRxSnr, this->lastRxRssi);
#else
snprintf(buffer, sizeof(buffer), "SNR: %.1f dB RSSI: %d", this->lastRxSnr, this->lastRxRssi);
#endif
display->drawString(display->getWidth() / 2 + x, yOffset, buffer);
}
#endif

View File

@@ -6,9 +6,13 @@
#include "input/RotaryEncoderImpl.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/SerialKeyboardImpl.h"
#include "input/TrackballInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
#include "input/i2cButton.h"
#include "modules/SystemCommandsModule.h"
#if HAS_TRACKBALL
#include "input/TrackballInterruptImpl1.h"
#endif
#if !MESHTASTIC_EXCLUDE_I2C
#include "input/cardKbI2cImpl.h"
#endif
@@ -135,13 +139,20 @@ void setupModules()
traceRouteModule = new TraceRouteModule();
#endif
#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
neighborInfoModule = new NeighborInfoModule();
if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) {
neighborInfoModule = new NeighborInfoModule();
}
#endif
#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
detectionSensorModule = new DetectionSensorModule();
if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) {
detectionSensorModule = new DetectionSensorModule();
}
#endif
#if !MESHTASTIC_EXCLUDE_ATAK
atakPluginModule = new AtakPluginModule();
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK,
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) {
atakPluginModule = new AtakPluginModule();
}
#endif
#if !MESHTASTIC_EXCLUDE_PKI
keyVerificationModule = new KeyVerificationModule();
@@ -163,7 +174,7 @@ void setupModules()
#endif
// Example: Put your module here
// new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER && !MESHTASTIC_EXCLUDE_I2C
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
@@ -186,6 +197,9 @@ void setupModules()
#endif
cardKbI2cImpl = new CardKbI2cImpl();
cardKbI2cImpl->init();
#if defined(M5STACK_UNITC6L)
i2cButton = new i2cButtonThread("i2cButtonThread");
#endif
#ifdef INPUTBROKER_MATRIX_TYPE
kbMatrixImpl = new KbMatrixImpl();
kbMatrixImpl->init();
@@ -207,7 +221,7 @@ void setupModules()
aLinuxInputImpl->init();
}
#endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
trackballInterruptImpl1 = new TrackballInterruptImpl1();
trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
@@ -227,11 +241,14 @@ void setupModules()
#if HAS_TELEMETRY
new DeviceTelemetryModule();
#endif
// TODO: How to improve this?
#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
new EnvironmentTelemetryModule();
if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
new EnvironmentTelemetryModule();
}
#if __has_include("Adafruit_PM25AQI.h")
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
new AirQualityTelemetryModule();
}
#endif
@@ -243,12 +260,16 @@ void setupModules()
#endif
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
new PowerTelemetryModule();
if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.power_measurement_enabled || moduleConfig.telemetry.power_screen_enabled)) {
new PowerTelemetryModule();
}
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \
!defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#if !MESHTASTIC_EXCLUDE_SERIAL
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
if (moduleConfig.has_serial && moduleConfig.serial.enabled &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
new SerialModule();
}
#endif
@@ -259,19 +280,26 @@ void setupModules()
audioModule = new AudioModule();
#endif
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
paxcounterModule = new PaxcounterModule();
if (moduleConfig.has_paxcounter && moduleConfig.paxcounter.enabled) {
paxcounterModule = new PaxcounterModule();
}
#endif
#endif
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
storeForwardModule = new StoreForwardModule();
if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) {
storeForwardModule = new StoreForwardModule();
}
#endif
#endif
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
externalNotificationModule = new ExternalNotificationModule();
if (moduleConfig.has_external_notification && moduleConfig.external_notification.enabled) {
externalNotificationModule = new ExternalNotificationModule();
}
#endif
#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
new RangeTestModule();
if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
new RangeTestModule();
#endif
} else {
#if !MESHTASTIC_EXCLUDE_ADMIN

View File

@@ -12,12 +12,12 @@ NodeInfoModule *nodeInfoModule;
bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *pptr)
{
auto p = *pptr;
if (mp.from == nodeDB->getNodeNum()) {
LOG_WARN("Ignoring packet supposed to be from our own node: %08x", mp.from);
return false;
}
auto p = *pptr;
if (p.is_licensed != owner.is_licensed) {
LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
return true;
@@ -44,7 +44,10 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal)
service->cancelSending(prevPacketId);
shorterTimeout = _shorterTimeout;
DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p = allocReply();
DEBUG_HEAP_AFTER("NodeInfoModule::sendOurNodeInfo", p);
if (p) { // Check whether we didn't ignore it
p->to = dest;
p->decoded.want_response = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&

View File

@@ -172,7 +172,10 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage,
telemetry.variant.device_metrics.uptime_seconds);
DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p = allocDataProtobuf(telemetry);
DEBUG_HEAP_AFTER("DeviceTelemetryModule::sendTelemetry", p);
p->to = dest;
p->decoded.want_response = false;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;

View File

@@ -90,8 +90,6 @@ int32_t PaxcounterModule::runOnce()
configuration.blecounter = 1;
configuration.blescantime = 0; // infinite
configuration.wificounter = 1;
configuration.wifi_channel_map = WIFI_CHANNEL_ALL;
configuration.wifi_channel_switch_interval = 50;
configuration.wifi_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.wifi_threshold, -80);
configuration.ble_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.ble_threshold, -80);
libpax_update_config(&configuration);

View File

@@ -11,6 +11,12 @@
#include <NimBLEDevice.h>
#include <mutex>
#ifdef NIMBLE_TWO
#include "NimBLEAdvertising.h"
#include "NimBLEExtAdvertising.h"
#include "PowerStatus.h"
#endif
NimBLECharacteristic *fromNumCharacteristic;
NimBLECharacteristic *BatteryCharacteristic;
NimBLECharacteristic *logRadioCharacteristic;
@@ -56,13 +62,18 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
{
PhoneAPI::onNowHasData(fromRadioNum);
LOG_DEBUG("BLE notify fromNum");
uint8_t cc = bleServer->getConnectedCount();
LOG_DEBUG("BLE notify fromNum: %d connections: %d", fromRadioNum, cc);
uint8_t val[4];
put_le32(val, fromRadioNum);
fromNumCharacteristic->setValue(val, sizeof(val));
#ifdef NIMBLE_TWO
fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE);
#else
fromNumCharacteristic->notify();
#endif
}
/// Check the current underlying physical link to see if the client is currently connected
@@ -79,7 +90,12 @@ static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
{
#ifdef NIMBLE_TWO
virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
#else
virtual void onWrite(NimBLECharacteristic *pCharacteristic)
#endif
{
auto val = pCharacteristic->getValue();
@@ -97,7 +113,11 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
{
#ifdef NIMBLE_TWO
virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
#else
virtual void onRead(NimBLECharacteristic *pCharacteristic)
#endif
{
int tries = 0;
bluetoothPhoneAPI->phoneWants = true;
@@ -107,9 +127,7 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
tries++;
}
std::lock_guard<std::mutex> guard(bluetoothPhoneAPI->nimble_mutex);
std::string fromRadioByteString(bluetoothPhoneAPI->fromRadioBytes,
bluetoothPhoneAPI->fromRadioBytes + bluetoothPhoneAPI->numBytes);
pCharacteristic->setValue(fromRadioByteString);
pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload
bluetoothPhoneAPI->setIntervalFromNow(0);
@@ -121,7 +139,17 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
{
#ifdef NIMBLE_TWO
public:
NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; }
private:
NimbleBluetooth *ble;
virtual uint32_t onPassKeyDisplay()
#else
virtual uint32_t onPassKeyRequest()
#endif
{
uint32_t passkey = config.bluetooth.fixed_pin;
@@ -133,7 +161,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
LOG_INFO("*** Enter passkey %d on the peer side ***", passkey);
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(std::to_string(passkey)));
meshtastic::BluetoothStatus newStatus(std::to_string(passkey));
bluetoothStatus->updateStatus(&newStatus);
#if HAS_SCREEN // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (screen) {
@@ -169,11 +198,16 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
return passkey;
}
#ifdef NIMBLE_TWO
virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo)
#else
virtual void onAuthenticationComplete(ble_gap_conn_desc *desc)
#endif
{
LOG_INFO("BLE authentication complete");
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
bluetoothStatus->updateStatus(&newStatus);
// Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (passkeyShowing) {
@@ -183,12 +217,23 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
}
}
#ifdef NIMBLE_TWO
virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo)
{
LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
}
virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason)
{
LOG_INFO("BLE disconnect reason: %d", reason);
#else
virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc)
{
LOG_INFO("BLE disconnect");
#endif
bluetoothStatus->updateStatus(
new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newStatus);
if (bluetoothPhoneAPI) {
std::lock_guard<std::mutex> guard(bluetoothPhoneAPI->nimble_mutex);
@@ -198,6 +243,10 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
bluetoothPhoneAPI->numBytes = 0;
bluetoothPhoneAPI->queue_size = 0;
}
#ifdef NIMBLE_TWO
// Restart Advertising
ble->startAdvertising();
#endif
}
};
@@ -249,7 +298,11 @@ int NimbleBluetooth::getRssi()
if (bleServer && isConnected()) {
auto service = bleServer->getServiceByUUID(MESH_SERVICE_UUID);
uint16_t handle = service->getHandle();
#ifdef NIMBLE_TWO
return NimBLEDevice::getClientByHandle(handle)->getRssi();
#else
return NimBLEDevice::getClientByID(handle)->getRssi();
#endif
}
return 0; // FIXME figure out where to source this
}
@@ -271,8 +324,11 @@ void NimbleBluetooth::setup()
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
}
bleServer = NimBLEDevice::createServer();
#ifdef NIMBLE_TWO
NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this);
#else
NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback();
#endif
bleServer->setCallbacks(serverCallbacks, true);
setupService();
startAdvertising();
@@ -316,8 +372,11 @@ void NimbleBluetooth::setupService()
NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic)
(uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1);
#ifdef NIMBLE_TWO
NimBLE2904 *batteryLevelDescriptor = BatteryCharacteristic->create2904();
#else
NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904);
#endif
batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8);
batteryLevelDescriptor->setNamespace(1);
batteryLevelDescriptor->setUnit(0x27ad);
@@ -327,11 +386,40 @@ void NimbleBluetooth::setupService()
void NimbleBluetooth::startAdvertising()
{
#ifdef NIMBLE_TWO
NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
NimBLEExtAdvertisement legacyAdvertising;
legacyAdvertising.setLegacyAdvertising(true);
legacyAdvertising.setScannable(true);
legacyAdvertising.setConnectable(true);
legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN);
if (powerStatus->getHasBattery() == 1) {
legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f));
}
legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID));
legacyAdvertising.setMinInterval(500);
legacyAdvertising.setMaxInterval(1000);
NimBLEExtAdvertisement legacyScanResponse;
legacyScanResponse.setLegacyAdvertising(true);
legacyScanResponse.setConnectable(true);
legacyScanResponse.setName(getDeviceName());
if (!pAdvertising->setInstanceData(0, legacyAdvertising)) {
LOG_ERROR("BLE failed to set legacyAdvertising");
} else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) {
LOG_ERROR("BLE failed to set legacyScanResponse");
} else if (!pAdvertising->start(0, 0, 0)) {
LOG_ERROR("BLE failed to start legacyAdvertising");
}
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
pAdvertising->addServiceUUID(MESH_SERVICE_UUID);
pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
pAdvertising->start(0);
#endif
}
/// Given a level between 0-100, update the BLE attribute
@@ -339,7 +427,11 @@ void updateBatteryLevel(uint8_t level)
{
if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) {
BatteryCharacteristic->setValue(&level, 1);
#ifdef NIMBLE_TWO
BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE);
#else
BatteryCharacteristic->notify();
#endif
}
}
@@ -354,7 +446,11 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
if (!bleServer || !isConnected() || length > 512) {
return;
}
#ifdef NIMBLE_TWO
logRadioCharacteristic->notify(logMessage, length, BLE_HS_CONN_HANDLE_NONE);
#else
logRadioCharacteristic->notify(logMessage, length, true);
#endif
}
void clearNVS()
@@ -364,4 +460,4 @@ void clearNVS()
ESP.restart();
#endif
}
#endif
#endif

View File

@@ -12,10 +12,15 @@ class NimbleBluetooth : BluetoothApi
bool isConnected();
int getRssi();
void sendLog(const uint8_t *logMessage, size_t length);
#if defined(NIMBLE_TWO)
void startAdvertising();
#endif
private:
void setupService();
#if !defined(NIMBLE_TWO)
void startAdvertising();
#endif
};
void setBluetoothEnable(bool enable);

View File

@@ -194,6 +194,8 @@
#define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO
#elif defined(T_LORA_PAGER)
#define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER
#elif defined(M5STACK_UNITC6L)
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
#endif
// -----------------------------------------------------------------------------

View File

@@ -169,9 +169,10 @@ void esp32Setup()
// #define APP_WATCHDOG_SECS 45
#define APP_WATCHDOG_SECS 90
#ifdef CONFIG_IDF_TARGET_ESP32C6
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_task_wdt_config_t *wdt_config = (esp_task_wdt_config_t *)malloc(sizeof(esp_task_wdt_config_t));
wdt_config->timeout_ms = APP_WATCHDOG_SECS * 1000;
wdt_config->idle_core_mask = 1 << 1;
wdt_config->trigger_panic = true;
res = esp_task_wdt_init(wdt_config);
assert(res == ESP_OK);

View File

@@ -59,7 +59,8 @@ void onConnect(uint16_t conn_handle)
LOG_INFO("BLE Connected to %s", central_name);
// Notify UI (or any other interested firmware components)
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
bluetoothStatus->updateStatus(&newStatus);
}
/**
* Callback invoked when a connection is dropped
@@ -74,7 +75,8 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason)
}
// Notify UI (or any other interested firmware components)
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newStatus);
}
void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value)
{
@@ -326,7 +328,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke
textkey += (char)passkey[i];
// Notify UI (or other components) of pairing event and passkey
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(textkey));
meshtastic::BluetoothStatus newStatus(textkey);
bluetoothStatus->updateStatus(&newStatus);
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (screen) {
@@ -398,12 +401,13 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu
{
if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
LOG_INFO("BLE pair success");
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
meshtastic::BluetoothStatus newConnectedStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
bluetoothStatus->updateStatus(&newConnectedStatus);
} else {
LOG_INFO("BLE pair failed");
// Notify UI (or any other interested firmware components)
bluetoothStatus->updateStatus(
new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
meshtastic::BluetoothStatus newDisconnectedStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newDisconnectedStatus);
}
// Todo: migrate this display code back into Screen class, and observe bluetoothStatus

View File

@@ -1,27 +1,5 @@
#include "../test_helpers.h"
// test data initialization
const int from = 0x11223344;
const int to = 0x55667788;
const int id = 0x9999;
// Helper function to create a test encrypted packet
meshtastic_MeshPacket create_test_encrypted_packet(uint32_t from, uint32_t to, uint32_t id, const char *data)
{
meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero;
packet.from = from;
packet.to = to;
packet.id = id;
packet.which_payload_variant = meshtastic_MeshPacket_encrypted_tag;
if (data) {
packet.encrypted.size = strlen(data);
memcpy(packet.encrypted.bytes, data, packet.encrypted.size);
}
return packet;
}
// Helper function for all encrypted packet assertions
void assert_encrypted_packet(const std::string &json, meshtastic_MeshPacket packet)
{
@@ -61,58 +39,20 @@ void assert_encrypted_packet(const std::string &json, meshtastic_MeshPacket pack
// Test encrypted packet serialization
void test_encrypted_packet_serialization()
{
meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero;
packet.from = 0x11223344;
packet.to = 0x55667788;
packet.id = 0x9999;
packet.which_payload_variant = meshtastic_MeshPacket_encrypted_tag;
// Add some dummy encrypted data
const char *encrypted_data = "encrypted_payload_data";
packet.encrypted.size = strlen(encrypted_data);
memcpy(packet.encrypted.bytes, encrypted_data, packet.encrypted.size);
const char *data = "encrypted_payload_data";
meshtastic_MeshPacket packet =
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast<const uint8_t *>(data), strlen(data),
meshtastic_MeshPacket_encrypted_tag);
std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check basic packet fields
TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end());
TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end());
TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end());
TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber());
// Check that it has encrypted data fields (not "payload" but "bytes" and "size")
TEST_ASSERT_TRUE(jsonObj.find("bytes") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["bytes"]->IsString());
TEST_ASSERT_TRUE(jsonObj.find("size") != jsonObj.end());
TEST_ASSERT_EQUAL(22, (int)jsonObj["size"]->AsNumber()); // strlen("encrypted_payload_data") = 22
// The encrypted data should be hex-encoded
std::string encrypted_hex = jsonObj["bytes"]->AsString();
TEST_ASSERT_TRUE(encrypted_hex.length() > 0);
// Should be twice the size of the original data (hex encoding)
TEST_ASSERT_EQUAL(44, encrypted_hex.length()); // 22 * 2 = 44
delete root;
assert_encrypted_packet(json, packet);
}
// Test empty encrypted packet
void test_empty_encrypted_packet()
{
const char *data = "";
meshtastic_MeshPacket packet = create_test_encrypted_packet(from, to, id, data);
meshtastic_MeshPacket packet =
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0, meshtastic_MeshPacket_encrypted_tag);
std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet);
assert_encrypted_packet(json, packet);

View File

@@ -1,42 +1,105 @@
#include "../test_helpers.h"
#include <memory>
// Helper function to test common packet fields and structure
void verify_text_message_packet_structure(const std::string &json, const char *expected_text)
{
TEST_ASSERT_TRUE(json.length() > 0);
// Use smart pointer for automatic memory management
std::unique_ptr<JSONValue> root(JSON::Parse(json.c_str()));
TEST_ASSERT_NOT_NULL(root.get());
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check basic packet fields - use helper function to reduce duplication
auto check_field = [&](const char *field, uint32_t expected_value) {
auto it = jsonObj.find(field);
TEST_ASSERT_TRUE(it != jsonObj.end());
TEST_ASSERT_EQUAL(expected_value, (uint32_t)it->second->AsNumber());
};
check_field("from", 0x11223344);
check_field("to", 0x55667788);
check_field("id", 0x9999);
// Check message type
auto type_it = jsonObj.find("type");
TEST_ASSERT_TRUE(type_it != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("text", type_it->second->AsString().c_str());
// Check payload
auto payload_it = jsonObj.find("payload");
TEST_ASSERT_TRUE(payload_it != jsonObj.end());
TEST_ASSERT_TRUE(payload_it->second->IsObject());
JSONObject payload = payload_it->second->AsObject();
auto text_it = payload.find("text");
TEST_ASSERT_TRUE(text_it != payload.end());
TEST_ASSERT_EQUAL_STRING(expected_text, text_it->second->AsString().c_str());
// No need for manual delete with smart pointer
}
// Test TEXT_MESSAGE_APP port
void test_text_message_serialization()
{
const char *test_text = "Hello Meshtastic!";
meshtastic_MeshPacket packet =
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, (const uint8_t *)test_text, strlen(test_text));
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast<const uint8_t *>(test_text), strlen(test_text));
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check basic packet fields
TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end());
TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end());
TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end());
TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber());
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("text", jsonObj["type"]->AsString().c_str());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
TEST_ASSERT_TRUE(payload.find("text") != payload.end());
TEST_ASSERT_EQUAL_STRING("Hello Meshtastic!", payload["text"]->AsString().c_str());
delete root;
verify_text_message_packet_structure(json, test_text);
}
// Test with nullptr to check robustness
void test_text_message_serialization_null()
{
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
verify_text_message_packet_structure(json, "");
}
// Test TEXT_MESSAGE_APP port with very long message (boundary testing)
void test_text_message_serialization_long_text()
{
// Test with actual message size limits
constexpr size_t MAX_MESSAGE_SIZE = 200; // Typical LoRa payload limit
std::string long_text(MAX_MESSAGE_SIZE, 'A');
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP,
reinterpret_cast<const uint8_t *>(long_text.c_str()), long_text.length());
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
verify_text_message_packet_structure(json, long_text.c_str());
}
// Test with message over size limit (should fail)
void test_text_message_serialization_oversized()
{
constexpr size_t OVERSIZED_MESSAGE = 250; // Over the limit
std::string oversized_text(OVERSIZED_MESSAGE, 'B');
meshtastic_MeshPacket packet = create_test_packet(
meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast<const uint8_t *>(oversized_text.c_str()), oversized_text.length());
// Should fail or return empty/error
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
// Should only verify first 234 characters for oversized messages
std::string expected_text = oversized_text.substr(0, 234);
verify_text_message_packet_structure(json, expected_text.c_str());
}
// Add test for malformed UTF-8 sequences
void test_text_message_serialization_invalid_utf8()
{
const uint8_t invalid_utf8[] = {0xFF, 0xFE, 0xFD, 0x00}; // Invalid UTF-8
meshtastic_MeshPacket packet =
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, invalid_utf8, sizeof(invalid_utf8) - 1);
// Should not crash, may produce replacement characters
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
}

View File

@@ -4,6 +4,10 @@
// Forward declarations for test functions
void test_text_message_serialization();
void test_text_message_serialization_null();
void test_text_message_serialization_long_text();
void test_text_message_serialization_oversized();
void test_text_message_serialization_invalid_utf8();
void test_position_serialization();
void test_nodeinfo_serialization();
void test_waypoint_serialization();
@@ -14,6 +18,7 @@ void test_telemetry_environment_metrics_missing_fields();
void test_telemetry_environment_metrics_complete_coverage();
void test_telemetry_environment_metrics_unset_fields();
void test_encrypted_packet_serialization();
void test_empty_encrypted_packet();
void setup()
{
@@ -21,6 +26,10 @@ void setup()
// Text message tests
RUN_TEST(test_text_message_serialization);
RUN_TEST(test_text_message_serialization_null);
RUN_TEST(test_text_message_serialization_long_text);
RUN_TEST(test_text_message_serialization_oversized);
RUN_TEST(test_text_message_serialization_invalid_utf8);
// Position tests
RUN_TEST(test_position_serialization);
@@ -41,6 +50,7 @@ void setup()
// Encrypted packet test
RUN_TEST(test_encrypted_packet_serialization);
RUN_TEST(test_empty_encrypted_packet);
UNITY_END();
}

View File

@@ -0,0 +1,28 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#define USB_VID 0x2886
#define USB_PID 0x0048
static const uint8_t TX = 16;
static const uint8_t RX = 17;
static const uint8_t SDA = 10;
static const uint8_t SCL = 8;
// Default SPI will be mapped to Radio
static const uint8_t MISO = 22;
static const uint8_t SCK = 20;
static const uint8_t MOSI = 21;
static const uint8_t SS = 6;
// #define SPI_MOSI (11)
// #define SPI_SCK (14)
// #define SPI_MISO (2)
// #define SPI_CS (13)
// #define SDCARD_CS SPI_CS
#endif /* Pins_Arduino_h */

View File

@@ -0,0 +1,35 @@
[env:m5stack-unitc6l]
extends = esp32c6_base
board = esp32-c6-devkitc-1
;OpenOCD flash method
;upload_protocol = esp-builtin
;Normal method
upload_protocol = esptool
;upload_port = /dev/ttyACM2
build_unflags =
-D HAS_BLUETOOTH
-D MESHTASTIC_EXCLUDE_BLUETOOTH
-D HAS_WIFI
lib_deps =
${esp32c6_base.lib_deps}
adafruit/Adafruit NeoPixel@^1.12.3
h2zero/NimBLE-Arduino@^2.3.6
build_flags =
${esp32c6_base.build_flags}
-D M5STACK_UNITC6L
-I variants/esp32c6/m5stack_unitc6l
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
-D HAS_BLUETOOTH=1
-D MESHTASTIC_EXCLUDE_WEBSERVER
-D MESHTASTIC_EXCLUDE_MQTT
-DCONFIG_BT_NIMBLE_EXT_ADV=1
-DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2
-D NIMBLE_TWO
monitor_speed=115200
lib_ignore =
NonBlockingRTTTL
libpax
build_src_filter =
${esp32c6_base.build_src_filter} +<../variants/esp32c6/m5stack_unitc6l>

View File

@@ -0,0 +1,74 @@
#include "driver/gpio.h"
#include <Arduino.h>
#include <Wire.h>
// I2C device addr
#define PI4IO_M_ADDR 0x43
// PI4IO registers
#define PI4IO_REG_CHIP_RESET 0x01
#define PI4IO_REG_IO_DIR 0x03
#define PI4IO_REG_OUT_SET 0x05
#define PI4IO_REG_OUT_H_IM 0x07
#define PI4IO_REG_IN_DEF_STA 0x09
#define PI4IO_REG_PULL_EN 0x0B
#define PI4IO_REG_PULL_SEL 0x0D
#define PI4IO_REG_IN_STA 0x0F
#define PI4IO_REG_INT_MASK 0x11
#define PI4IO_REG_IRQ_STA 0x13
// PI4IO
#define setbit(x, y) x |= (0x01 << y)
#define clrbit(x, y) x &= ~(0x01 << y)
#define reversebit(x, y) x ^= (0x01 << y)
#define getbit(x, y) ((x) >> (y)&0x01)
void i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value)
{
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(addr, 1);
*value = Wire.read();
}
/*******************************************************************/
void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value)
{
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.write(value);
Wire.endTransmission();
}
/*******************************************************************/
void c6l_init()
{
// P7 LoRa Reset
// P6 RF Switch
// P5 LNA Enable
printf("pi4io_init\n");
uint8_t in_data;
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, 0xFF);
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, &in_data);
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11000000); // 0: input 1: output
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00111100); // 使用到的引脚关闭High-Impedance
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11000011); // pull up/down select, 0 down, 1 up
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11000011); // pull up/down enable, 0 disable, 1 enable
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 默认高电平, 按键按下触发中断
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 中断使能 0 enable, 1 disable
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // 默认输出为0
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // 读取IRQ_STA清除标志
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, &in_data);
setbit(in_data, 6); // HIGH
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, in_data);
}

View File

@@ -0,0 +1,52 @@
void c6l_init();
#define HAS_GPS 1
#define GPS_RX_PIN 4
#define GPS_TX_PIN 5
#define I2C_SDA 10
#define I2C_SCL 8
#define PIN_BUZZER 11
#define HAS_NEOPIXEL // Enable the use of neopixels
#define NEOPIXEL_COUNT 1 // How many neopixels are connected
#define NEOPIXEL_DATA 2 // gpio pin used to send data to the neopixels
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use
#define ENABLE_AMBIENTLIGHTING // Turn on Ambient Lighting
// #define BUTTON_PIN 9
#define BUTTON_EXTENDER
#undef LORA_SCK
#undef LORA_MISO
#undef LORA_MOSI
#undef LORA_CS
// WaveShare Core1262-868M OK
// https://www.waveshare.com/wiki/Core1262-868M
#define USE_SX1262
#define LORA_MISO 22
#define LORA_SCK 20
#define LORA_MOSI 21
#define LORA_CS 23
#define LORA_RESET RADIOLIB_NC
#define LORA_DIO1 7
#define LORA_BUSY 19
#define SX126X_CS LORA_CS
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_BUSY
#define SX126X_RESET LORA_RESET
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 3.0
#define USE_SPISSD1306
#ifdef USE_SPISSD1306
#define SSD1306_NSS 6 // CS
#define SSD1306_RS 18 // DC
#define SSD1306_RESET 15
// #define OLED_DG 1
#endif
#define SCREEN_TRANSITION_FRAMERATE 10
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness

View File

@@ -2,7 +2,7 @@
extends = esp32s3_base
board = bpi_picow_esp32_s3
board_check = true
board_build.partitions = default_8MB.csv
board_build.partitions = partition-table-8MB.csv
;OpenOCD flash method
;upload_protocol = esp-builtin
;Normal method

View File

@@ -70,6 +70,7 @@ build_flags =
${ft5x06.build_flags}
-D LGFX_SCREEN_WIDTH=240
-D LGFX_SCREEN_HEIGHT=320
-D DISPLAY_SIZE=320x240 ; landscape mode
-D LGFX_PANEL=ST7789
-D LGFX_ROTATION=1
-D LGFX_TOUCH_X_MIN=0

View File

@@ -1,18 +1,31 @@
; Seeed Studio SenseCAP Indicator
; note: does not work with vscode platformio plugin; needs pioarduino IDE plugin instead
[env:seeed-sensecap-indicator]
extends = esp32s3_base
platform =
https://github.com/pioarduino/platform-espressif32.git#55.03.30-1
platform_packages =
platformio/framework-arduinoespressif32 @ https://github.com/mverch67/arduino-esp32/archive/aef7fef6de3329ed6f75512d46d63bba12b09bb5.zip ; add_tca9535 (based on 2.0.16)
platformio/framework-arduinoespressif32 @ https://github.com/mverch67/arduino-esp32#ab7f873773424561fa0d8434034960a36d335fcb
board = seeed-sensecap-indicator
board_check = true
board_build.partitions = default_8MB.csv
board_build.partitions = partition-table-8MB.csv
upload_protocol = esptool
build_flags = ${esp32_base.build_flags}
build_unflags =
-DCONFIG_BT_NIMBLE_ENABLED
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
-DCONFIG_BT_NIMBLE_MAX_CCCDS=20
-DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
build_flags = ${esp32_base.build_flags} -D lcd_periph_signals=lcd_periph_rgb_signals -mtext-section-literals -ffat-lto-objects
-Ivariants/esp32s3/seeed-sensecap-indicator
-DSENSECAP_INDICATOR
-DCONFIG_ARDUHAL_LOG_COLORS
-DARDUINO_HAL_LOG_LEVEL=5
-DARDUHAL_LOG_MAXIMUM_LEVEL=5
-DARDUHAL_LOG_COLORS=1
-DUSE_ARDUINO_HAL_GPIO
-DRADIOLIB_DEBUG_SPI=0
-DRADIOLIB_DEBUG_PROTOCOL=0
-DRADIOLIB_DEBUG_BASIC=0
@@ -20,13 +33,72 @@ build_flags = ${esp32_base.build_flags}
-DRADIOLIB_SPI_PARANOID=0
-DIO_EXPANDER=0x40
-DIO_EXPANDER_IRQ=42
;-DIO_EXPANDER_DEBUG
-DUSE_ARDUINO_HAL_GPIO
; -DIO_EXPANDER_DEBUG
lib_deps = ${esp32s3_base.lib_deps}
https://github.com/mverch67/LovyanGFX/archive/4c76238c1344162a234ae917b27651af146d6fb2.zip
earlephilhower/ESP8266Audio@^1.9.9
earlephilhower/ESP8266SAM@^1.0.1
https://github.com/mverch67/LovyanGFX/archive/a1c1278fc9116d1c6cb15a7bb14565aef59a9a97.zip
custom_component_remove =
espressif/esp_hosted
espressif/esp_wifi_remote
espressif/esp_modem
espressif/esp-dsp
espressif/esp32-camera
espressif/libsodium
espressif/esp-modbus
espressif/qrcode
espressif/esp_insights
espressif/esp_diag_data_store
espressif/esp_diagnostics
espressif/esp_rainmaker
espressif/rmaker_common
espressif/network_provisioning
chmorgan/esp-libhelix-mp3
custom_sdkconfig =
CONFIG_AUTOSTART_ARDUINO=y
CONFIG_LOG_DEFAULT_LEVEL=4
CONFIG_LOG_MAXIMUM_LEVEL=4
CONFIG_LOG_COLORS=y
CONFIG_ARDUHAL_LOG_COLORS=y
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_SPEED_120M=y
CONFIG_SPIRAM_SPEED=120
CONFIG_SPIRAM_XIP_FROM_PSRAM=y
CONFIG_LCD_RGB_ISR_IRAM_SAFE=y
CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y
CONFIG_I2S_ISR_IRAM_SAFE=y
CONFIG_GDMA_ISR_IRAM_SAFE=y
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32S3_DATA_CACHE_64KB=y
CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK=y
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y
CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y
CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y
CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM=y
CONFIG_ESP_WIFI_IRAM_OPT=n
CONFIG_ESP32_WIFI_RX_IRAM_OPT=n
CONFIG_SPIRAM_CACHE_LIBCHAR_IN_IRAM=n
CONFIG_SPIRAM_CACHE_LIBSTR_IN_IRAM=n
CONFIG_SPIRAM_CACHE_LIBMISC_IN_IRAM=n
CONFIG_SPIRAM_CACHE_LIBTIME_IN_IRAM=n
CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=0
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_CONTROLLER_ENABLED=y
CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
CONFIG_BT_NIMBLE_MAX_CCCDS=20
CONFIG_BT_NIMBLE_CPP_LOG_LEVEL=1
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
CONFIG_ESPTOOLPY_FLASHFREQ_120M=y
CONFIG_ESPTOOLPY_FLASHFREQ="120m"
CONFIG_ESP_TASK_WDT_INIT=n
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
CONFIG_COMPILER_OPTIMIZATION_PERF=y
CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y
[env:seeed-sensecap-indicator-tft]
@@ -37,14 +109,19 @@ upload_speed = 460800
build_flags =
${env:seeed-sensecap-indicator.build_flags}
-D INPUTDRIVER_BUTTON_TYPE=38
-D MESHTASTIC_EXCLUDE_WEBSERVER=1
-D MESHTASTIC_EXCLUDE_SERIAL=1
-D MESHTASTIC_EXCLUDE_SOCKETAPI=1
-D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
-D HAS_TELEMETRY=0
-D CONFIG_DISABLE_HAL_LOCKS=1
-D HAS_SCREEN=1
-D HAS_TFT=1
-D DISPLAY_SET_RESOLUTION
-D RAM_SIZE=4096
-D LV_LVGL_H_INCLUDE_SIMPLE
-D LV_CONF_INCLUDE_SIMPLE
-D LV_COMP_CONF_INCLUDE_SIMPLE
-D RAM_SIZE=3072
-D LV_LVGL_H_INCLUDE_SIMPLE
-D LV_CONF_INCLUDE_SIMPLE
-D LV_COMP_CONF_INCLUDE_SIMPLE
-D LV_USE_SYSMON=0
-D LV_USE_PROFILER=0
-D LV_USE_PERF_MONITOR=0

View File

@@ -4,7 +4,7 @@
// This board has a serial coprocessor for sensor readings
#define SENSOR_RP2040_TXD 19
#define SENSOR_RP2040_RXD 20
#define SENSOR_PORT_NUM 2
#define SENSOR_PORT_NUM UART_NUM_2
#define SENSOR_BAUD_RATE 115200
#define BUTTON_PIN 38

View File

@@ -0,0 +1,15 @@
#include "RadioLib.h"
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
// mode DIO5 DIO6
{LR11x0::MODE_STBY, {LOW, LOW}},
{LR11x0::MODE_RX, {LOW, HIGH}},
{LR11x0::MODE_TX, {HIGH, LOW}},
{LR11x0::MODE_TX_HP, {HIGH, LOW}},
{LR11x0::MODE_TX_HF, {LOW, LOW}},
{LR11x0::MODE_GNSS, {LOW, LOW}},
{LR11x0::MODE_WIFI, {LOW, LOW}},
END_OF_MODE_TABLE,
};

View File

@@ -105,14 +105,16 @@
// LoRa
#define USE_SX1262
#define USE_SX1268
#define USE_SX1280
#define USE_LR1121
#define LORA_SCK 35
#define LORA_MISO 33
#define LORA_MOSI 34
#define LORA_CS 36
#define LORA_RESET 47
#define LORA_DIO0 -1 // a No connect on the SX1262 module
#define LORA_RESET 47
#define LORA_DIO1 14 // SX1262 IRQ
#define LORA_DIO2 48 // SX1262 BUSY
#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
@@ -123,3 +125,18 @@
#define SX126X_RESET LORA_RESET
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 3.0
#define SX128X_CS LORA_CS
#define SX128X_DIO1 LORA_DIO1
#define SX128X_BUSY LORA_DIO2
#define SX128X_RESET LORA_RESET
#define LR1121_IRQ_PIN LORA_DIO1
#define LR1121_NRESET_PIN LORA_RESET
#define LR1121_BUSY_PIN LORA_DIO2
#define LR1121_SPI_NSS_PIN LORA_CS
#define LR1121_SPI_SCK_PIN LORA_SCK
#define LR1121_SPI_MOSI_PIN LORA_MOSI
#define LR1121_SPI_MISO_PIN LORA_MISO
#define LR11X0_DIO3_TCXO_VOLTAGE 3.0
#define LR11X0_DIO_AS_RF_SWITCH

View File

@@ -3,7 +3,7 @@
[env:unphone]
extends = esp32s3_base
board = unphone
board_build.partitions = default_8MB.csv
board_build.partitions = partition-table-8MB.csv
upload_speed = 921600
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
@@ -20,6 +20,7 @@ build_flags =
-D UNPHONE_LORA=0
-D UNPHONE_FACTORY_MODE=0
-D USE_SX127x
-D SDCARD_CS=43
build_src_filter =
${esp32s3_base.build_src_filter}
@@ -41,6 +42,7 @@ build_flags =
-D HAS_SCREEN=1
-D HAS_TFT=1
-D HAS_SDCARD
-D SDCARD_CS=43
-D DISPLAY_SET_RESOLUTION
-D RAM_SIZE=6144
-D LV_CACHE_DEF_SIZE=2097152

View File

@@ -52,7 +52,6 @@
#undef GPS_TX_PIN
#define SD_SPI_FREQUENCY 25000000
#define SDCARD_CS 43
#define LED_PIN 13 // the red part of the RGB LED
#define LED_STATE_ON 0 // State when LED is lit

View File

@@ -2,4 +2,4 @@
#define CANNED_MESSAGE_MODULE_ENABLE 1
#define HAS_GPS 1
#define MAX_RX_TOPHONE settingsMap[maxtophone]
#define MAX_NUM_NODES settingsMap[maxnodes]
#define MAX_NUM_NODES settingsMap[maxnodes]