Compare commits

...

84 Commits

Author SHA1 Message Date
Jonathan Bennett
ee89bdd569 ifdef gate the new boolean to NIMBLE_TWO devices 2025-09-19 19:51:26 -05:00
Jonathan Bennett
cc1c568916 Don't probe rare GPS speeds on esp32c6l to avoid uart-related crash 2025-09-19 18:55:30 -05:00
Jonathan Bennett
0fecbbf86b nimble 2.x deinit workaround 2025-09-19 18:54:41 -05:00
Ben Meadors
9b6a7ed3bb Fix icon 2025-09-19 16:00:24 -05:00
github-actions[bot]
fdc8796052 Update protobufs (#8045)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-09-19 15:50:33 -05:00
Jason P
f32e06a321 Update Protobuf usage, add MLS, fix clock (#8041) 2025-09-19 10:51:07 -05:00
Austin
8095261dfd PPA: Enable Ubuntu 25.10 (questing) (#7940) 2025-09-19 10:18:08 -05:00
Tom
72b9a02f3e (resubmission) Manual GitHub actions to allow building one target or arch (#7997)
* Reset the modified files

* Fix some changes

* Fix some changes

* Trunk. That is all.

---------

Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com>
2025-09-19 10:16:46 -05:00
Quency-D
af26408d73 Add a new GPS model CM121. (#7852)
* Add a new GPS model CM121.

* Add CM121 to Unicore.

* Trunk fixes, remove unneded NMEA lines

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-09-19 10:05:41 -05:00
Jason P
c8f69913d6 Add formatting and menu picking for other GPS format options (#7974)
* Add back options for other GPS format options

* Rename variables and don't overlap elements

* Fix default value

* Should probably add a menu while I'm here!

* Shorten names just a bit to fit on screens

* Fix off by one

* Labels try to make things better

* Missed a label
2025-09-19 09:56:04 -05:00
github-actions[bot]
42fbb62f18 Update protobufs (#8038)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-09-19 08:47:53 -05:00
Jason P
e6adb197e4 Add formatting and menu picking for other GPS format options (#7974)
* Add back options for other GPS format options

* Rename variables and don't overlap elements

* Fix default value

* Should probably add a menu while I'm here!

* Shorten names just a bit to fit on screens

* Fix off by one

* Labels try to make things better

* Missed a label
2025-09-19 08:47:31 -05:00
Jason P
8264d4d65e BaseUI Updates (#7787)
* Account for low resolution wide screen OLEDs

* Allow picking of Device Role and new Display Formatter for Device Role

* Add remainder of client roles to display formatter

* Don't update the role unless you pick a value

* Mascots are fun

* Fix warnings during compile time

* Improve some menus

* Mascots need to work everywhere

* Update Chirpy image

* Fix Trunk

* Update protobufs

* Add date to Clock screen

* Analog clocks love dates too

* Finalize date moves for analog clock
2025-09-19 08:44:14 -05:00
Jason P
a1cf305336 Show GPS Date properly in drawCommonHeader (#7887)
* Commit good code that is sustainable

* Fix new build errors
2025-09-19 08:33:37 -05:00
Trent V.
7b2ff7e196 updated shebang to use a more standard path for bash (#7922)
Signed-off-by: Trenton VanderWert <trenton.vanderwert@gmail.com>
2025-09-19 08:30:01 -05:00
Jonathan Bennett
cc579dd0bd Portduino config refactor (#7796)
* Start portduino_config refactor

* refactor GPIOs to new portduino_config

* More portduino_config work

* More conversion to portduino_config

* Finish portduino_config transition

* trunk

* yaml output work

* Simplify the GPIO config

* Trunk
2025-09-19 08:24:35 -05:00
Ben Meadors
6a92358b68 Fix 2025-09-19 07:22:23 -05:00
HarukiToreda
e20a91b945 Added Last Coordinate counter to Position screen (#7865)
Adding a counter to show the last time a GPS coordinate was detected to ensure the user is aware how long since the coordinate updated or to identify any errors.
2025-09-19 07:00:44 -05:00
Jason P
3fbe7fd8b2 BaseUI Updates (#7787)
* Account for low resolution wide screen OLEDs

* Allow picking of Device Role and new Display Formatter for Device Role

* Add remainder of client roles to display formatter

* Don't update the role unless you pick a value

* Mascots are fun

* Fix warnings during compile time

* Improve some menus

* Mascots need to work everywhere

* Update Chirpy image

* Fix Trunk

* Update protobufs

* Add date to Clock screen

* Analog clocks love dates too

* Finalize date moves for analog clock
2025-09-19 06:59:33 -05:00
Ben Meadors
e2ce369782 Fixes 2025-09-19 06:29:18 -05:00
Ben Meadors
b14e5770d5 Merge pull request #7873 from compumike/compumike/client-base-role
Add `CLIENT_BASE` role: `ROUTER` for favorites, `CLIENT` otherwise (for attic/roof nodes!)
2025-09-18 20:40:39 -05:00
Jonathan Bennett
68ba3b315c Auto-favorite remote admin node 2025-09-18 20:37:56 -05:00
Michael
2bafac242e Feature: Seamless Cross-Preset Communication via UDP Multicast Bridging (#7753)
* Added compatibility between nodes on different Presets through `Mesh via UDP`

* Optimize multicast handling and channel mapping

- FloodingRouter: remove redundant UDP-encrypted rebroadcast suppression.
- Router: guard multicast fallback with HAS_UDP_MULTICAST and map fallback-decoded packets
  to the local default channel via isDefaultChannel()
- UdpMulticastHandler: set transport_mechanism only after successful decode

* trunk fmt

* Move setting transport mechanism.

---------

Co-authored-by: GUVWAF <thijs@havinga.eu>
2025-09-18 20:37:05 -05:00
Ben Meadors
39648e609a Merge pull request #8004 from compumike/compumike/debug-heap-add-free-heap-debugging-to-all-log-lines
When `DEBUG_HEAP` is defined, add free heap bytes to every log line in `RedirectablePrint::log_to_serial`
2025-09-18 20:36:44 -05:00
HarukiToreda
dcd53eb7cb Phone GPS display on Position Screen for BaseUI (#7875)
* Phone GPS display on Position Screen

This is a PR to show when a phone shares GPS location with the node so you can reliably know what coordinate is being shared with the Mesh.
2025-09-18 20:29:46 -05:00
renovate[bot]
7821919fae Update actions/setup-python action to v6 (#8033)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 20:12:28 -05:00
renovate[bot]
f083864f1f Update actions/download-artifact action to v5 (#8032)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 19:56:57 -05:00
renovate[bot]
8e1da8561e Update actions/checkout action to v5 (#8031)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 19:56:47 -05:00
Jason P
953fcca304 BaseUI Show/Hide Frame Functionality (#7382)
* Rename System Frame (from Memory) in code base

* Create menu options to Show/Hide frames: Node Lists, Bearings, Position, LoRa, Clock and Favorites frames

* Move Region Picker into submenu

* Tweak wording for Send Position vs Node Info if the device has GPS
2025-09-18 19:55:08 -05:00
Tom
c73fe85ec8 (resubmission) Manual GitHub actions to allow building one target or arch (#7997)
* Reset the modified files

* Fix some changes

* Fix some changes

* Trunk. That is all.

---------

Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com>
2025-09-18 19:51:14 -05:00
WillyJL
9345bdcb22 T-Lora Pager: Support LR1121 and SX1280 models (#7956)
* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs
2025-09-18 19:49:28 -05:00
Markus
ec29100a88 Allow Left / Right Events for selection and improve encoder responsives (#8016)
* Allow Left / Right Events for selection and improve encoder responsives

* add define for ROTARY_DELAY
2025-09-18 19:29:09 -05:00
github-actions[bot]
e3772858b3 Automated version bumps (#8028)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-09-18 18:20:21 -05:00
Markus
2567c03a3f Fix init for InputEvent (#8015) 2025-09-18 18:15:50 -05:00
renovate[bot]
188283b382 Update actions/download-artifact action to v5 (#8021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 05:56:05 -05:00
github-actions[bot]
953fdc9eed Upgrade trunk (#8025)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-09-18 05:55:44 -05:00
renovate[bot]
ec7415b3fd Update actions/setup-python action to v6 (#8023)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 05:55:24 -05:00
renovate[bot]
a70ffae82c Update actions/checkout action to v5 (#8020)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 05:51:22 -05:00
renovate[bot]
6a8732bbaa Update Adafruit BusIO to v1.17.3 (#8018)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 05:51:02 -05: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
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
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
131 changed files with 5148 additions and 1179 deletions

View File

@@ -76,7 +76,7 @@ bool loopCanSleep()
// Called just prior to starting Meshtastic. Allows for setting config values before startup. // Called just prior to starting Meshtastic. Allows for setting config values before startup.
void lateInitVariant() void lateInitVariant()
{ {
settingsMap[logoutputlevel] = level_error; portduino_config.logoutputlevel = level_error;
channelFile.channels[0] = meshtastic_Channel{ channelFile.channels[0] = meshtastic_Channel{
.has_settings = true, .has_settings = true,
.settings = .settings =
@@ -132,7 +132,7 @@ int portduino_main(int argc, char **argv); // Renamed "main" function from Mesht
// Start Meshtastic in a thread and wait till it has reached the ON state. // Start Meshtastic in a thread and wait till it has reached the ON state.
int LLVMFuzzerInitialize(int *argc, char ***argv) int LLVMFuzzerInitialize(int *argc, char ***argv)
{ {
settingsMap[maxtophone] = 5; portduino_config.maxtophone = 5;
meshtasticThread = std::thread([program = *argv[0]]() { meshtasticThread = std::thread([program = *argv[0]]() {
char nodeIdStr[12]; char nodeIdStr[12];

View File

@@ -11,11 +11,6 @@ runs:
ref: ${{github.event.pull_request.head.ref}} ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}} repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Uncomment build epoch
shell: bash
run: |
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
- name: Install dependencies - name: Install dependencies
shell: bash shell: bash
run: | run: |

500
.github/workflows/build_one_arch.yml vendored Normal file
View File

@@ -0,0 +1,500 @@
name: Build One Arch
on:
workflow_dispatch:
inputs:
arch:
type: choice
options:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- native
jobs:
setup:
strategy:
fail-fast: false
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
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 ${{inputs.arch}} extra)
else
TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr)
fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
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@v5
- 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 }}
build-esp32:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}}
needs: [setup, version]
strategy:
fail-fast: false
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:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32s3
build-esp32c3:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c3
build-esp32c6:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c6
build-nrf52840:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: nrf52840
build-rp2040:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2040
build-rp2350:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2350
build-stm32:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: stm32
build-debian-src:
if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
build_location: local
secrets: inherit
package-pio-deps-native-tft:
if: ${{ inputs.arch == 'native' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
secrets: inherit
test-native:
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' }}
uses: ./.github/workflows/test_native.yml
docker-deb-amd64:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-deb-amd64-tft:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
pio_env: native-tft
docker-alp-amd64:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-alp-amd64-tft:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
pio_env: native-tft
docker-deb-arm64:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm64
runs-on: ubuntu-24.04-arm
push: false
docker-deb-armv7:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm/v7
runs-on: ubuntu-24.04-arm
push: false
gather-artifacts:
permissions:
contents: write
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@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v5
with:
path: ./
pattern: firmware-${{inputs.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-${{inputs.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@v5
with:
name: firmware-${{inputs.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-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v4
with:
name: debug-elfs-${{inputs.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-${{inputs.arch}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}
release-artifacts:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
- version
- gather-artifacts
- build-debian-src
- package-pio-deps-native-tft
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Create release
uses: softprops/action-gh-release@v2
id: create_release
with:
draft: true
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
uses: actions/download-artifact@v5
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v5
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output/pio-deps-native-tft
- name: Zip Linux sources
working-directory: output
run: |
zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
# For diagnostics
- name: Display structure of downloaded files
run: ls -lR
- name: Add Linux sources to GtiHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-firmware:
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
- name: Display structure of downloaded files
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v5
with:
name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
merge-multiple: true
path: ./elfs
- name: Zip debug elfs
run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
# For diagnostics
- name: Display structure of downloaded files
run: ls -lR
- name: Add bins and debug elfs to GitHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-firmware:
runs-on: ubuntu-24.04
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-firmware, version]
env:
targets: |-
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Publish firmware to meshtastic.github.io
uses: peaceiris/actions-gh-pages@v4
env:
# On event/* branches, use the event name as the destination prefix
DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
with:
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
external_repository: meshtastic/meshtastic.github.io
publish_branch: master
publish_dir: ./publish
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
keep_files: true
user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com
commit_message: ${{ needs.version.outputs.long }}
enable_jekyll: true

395
.github/workflows/build_one_target.yml vendored Normal file
View File

@@ -0,0 +1,395 @@
name: Build One Target
on:
workflow_dispatch:
inputs:
arch:
type: choice
options:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- native
target:
type: string
required: false
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
# find-target:
# type: boolean
# default: true
# description: 'Find the available targets'
jobs:
find-targets:
if: ${{ inputs.target == '' }}
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
run: |
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra)
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "Targets:" >> $GITHUB_STEP_SUMMARY
echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY
version:
if: ${{ inputs.target != '' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- 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 }}
build-arch:
if: ${{ inputs.target != '' && inputs.arch != 'native' }}
needs: [version]
strategy:
fail-fast: false
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ inputs.target }}
platform: ${{ inputs.arch }}
build-debian-src:
if: ${{ github.repository == 'meshtastic/firmware' && inputs.arch == 'native' }}
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
build_location: local
secrets: inherit
package-pio-deps-native-tft:
if: ${{ inputs.arch == 'native' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
secrets: inherit
test-native:
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }}
uses: ./.github/workflows/test_native.yml
docker-deb-amd64:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-deb-amd64-tft:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
pio_env: native-tft
docker-alp-amd64:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
docker-alp-amd64-tft:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
pio_env: native-tft
docker-deb-arm64:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm64
runs-on: ubuntu-24.04-arm
push: false
docker-deb-armv7:
if: ${{ inputs.target != '' && inputs.arch == 'native' }}
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
platform: linux/arm/v7
runs-on: ubuntu-24.04-arm
push: false
gather-artifacts:
permissions:
contents: write
pull-requests: write
strategy:
fail-fast: false
runs-on: ubuntu-latest
needs: [version, build-arch]
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v5
with:
path: ./
pattern: firmware-*-*
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-${{inputs.target}}-${{ 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@v5
with:
pattern: firmware-*-${{ 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-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v4
with:
name: debug-elfs-${{inputs.target}}-${{ 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-${{inputs.target}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}
release-artifacts:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
- version
- gather-artifacts
- build-debian-src
- package-pio-deps-native-tft
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Create release
uses: softprops/action-gh-release@v2
id: create_release
with:
draft: true
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
uses: actions/download-artifact@v5
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v5
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output/pio-deps-native-tft
- name: Zip Linux sources
working-directory: output
run: |
zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
# For diagnostics
- name: Display structure of downloaded files
run: ls -lR
- name: Add Linux sources to GtiHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-firmware:
strategy:
fail-fast: false
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-*-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
- name: Display structure of downloaded files
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v5
with:
pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip
merge-multiple: true
path: ./elfs
- name: Zip debug elfs
run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs
# For diagnostics
- name: Display structure of downloaded files
run: ls -lR
- name: Add bins and debug elfs to GitHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-firmware:
runs-on: ubuntu-24.04
if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }}
needs: [release-firmware, version]
env:
targets: |-
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Publish firmware to meshtastic.github.io
uses: peaceiris/actions-gh-pages@v4
env:
# On event/* branches, use the event name as the destination prefix
DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
with:
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
external_repository: meshtastic/meshtastic.github.io
publish_branch: master
publish_dir: ./publish
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
keep_files: true
user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com
commit_message: ${{ needs.version.outputs.long }}
enable_jekyll: true

View File

@@ -21,18 +21,20 @@ permissions:
jobs: jobs:
docker-multiarch: docker-multiarch:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_manifest.yml uses: ./.github/workflows/docker_manifest.yml
with: with:
release_channel: daily release_channel: daily
secrets: inherit secrets: inherit
package-ppa: package-ppa:
if: github.repository == 'meshtastic/firmware'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
series: series:
- jammy # 22.04 - jammy # 22.04 LTS
- noble # 24.04 - noble # 24.04 LTS
- plucky # 25.04 - plucky # 25.04
- questing # 25.10 - questing # 25.10
uses: ./.github/workflows/package_ppa.yml uses: ./.github/workflows/package_ppa.yml
@@ -42,6 +44,7 @@ jobs:
secrets: inherit secrets: inherit
package-obs: package-obs:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/package_obs.yml uses: ./.github/workflows/package_obs.yml
with: with:
obs_project: network:Meshtastic:daily obs_project: network:Meshtastic:daily
@@ -49,6 +52,7 @@ jobs:
secrets: inherit secrets: inherit
hook-copr: hook-copr:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/hook_copr.yml uses: ./.github/workflows/hook_copr.yml
with: with:
copr_project: daily copr_project: daily

View File

@@ -3,7 +3,7 @@ concurrency:
group: ci-${{ github.head_ref || github.run_id }} group: ci-${{ github.head_ref || github.run_id }}
cancel-in-progress: true cancel-in-progress: true
on: on:
# # Triggers the workflow on push but only for the master branch # # Triggers the workflow on push but only for the main branches
push: push:
branches: branches:
- master - master
@@ -27,6 +27,7 @@ on:
jobs: jobs:
setup: setup:
if: github.repository == 'meshtastic/firmware'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -70,6 +71,7 @@ jobs:
check: ${{ steps.jsonStep.outputs.check }} check: ${{ steps.jsonStep.outputs.check }}
version: version:
if: github.repository == 'meshtastic/firmware'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
@@ -91,7 +93,7 @@ jobs:
matrix: ${{ fromJson(needs.setup.outputs.check) }} matrix: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }} if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Build base - name: Build base
@@ -204,10 +206,11 @@ jobs:
secrets: inherit secrets: inherit
test-native: test-native:
if: ${{ !contains(github.ref_name, 'event/') }} if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }}
uses: ./.github/workflows/test_native.yml uses: ./.github/workflows/test_native.yml
docker-deb-amd64: docker-deb-amd64:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml uses: ./.github/workflows/docker_build.yml
with: with:
distro: debian distro: debian
@@ -216,6 +219,7 @@ jobs:
push: false push: false
docker-deb-amd64-tft: docker-deb-amd64-tft:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml uses: ./.github/workflows/docker_build.yml
with: with:
distro: debian distro: debian
@@ -225,6 +229,7 @@ jobs:
pio_env: native-tft pio_env: native-tft
docker-alp-amd64: docker-alp-amd64:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml uses: ./.github/workflows/docker_build.yml
with: with:
distro: alpine distro: alpine
@@ -233,6 +238,7 @@ jobs:
push: false push: false
docker-alp-amd64-tft: docker-alp-amd64-tft:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml uses: ./.github/workflows/docker_build.yml
with: with:
distro: alpine distro: alpine
@@ -242,6 +248,7 @@ jobs:
pio_env: native-tft pio_env: native-tft
docker-deb-arm64: docker-deb-arm64:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml uses: ./.github/workflows/docker_build.yml
with: with:
distro: debian distro: debian
@@ -250,6 +257,7 @@ jobs:
push: false push: false
docker-deb-armv7: docker-deb-armv7:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/docker_build.yml uses: ./.github/workflows/docker_build.yml
with: with:
distro: debian distro: debian
@@ -258,6 +266,8 @@ jobs:
push: false push: false
gather-artifacts: gather-artifacts:
# trunk-ignore(checkov/CKV2_GHA_1)
if: github.repository == 'meshtastic/firmware'
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
@@ -357,7 +367,7 @@ jobs:
release-artifacts: release-artifacts:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }} if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
outputs: outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
needs: needs:
@@ -432,7 +442,7 @@ jobs:
- rp2350 - rp2350
- stm32 - stm32
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }} if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware'}}
needs: [release-artifacts, version] needs: [release-artifacts, version]
steps: steps:
- name: Checkout - name: Checkout

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@v5
- uses: actions/setup-python@v6
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@v5
- 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@v5
- 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@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v5
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@v5
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@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Create release
uses: softprops/action-gh-release@v2
id: create_release
with:
draft: true
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
uses: actions/download-artifact@v5
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v5
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output/pio-deps-native-tft
- name: Zip Linux sources
working-directory: output
run: |
zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
# For diagnostics
- name: Display structure of downloaded files
run: ls -lR
- name: Add Linux sources to GtiHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-firmware:
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-${{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@v5
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@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Publish firmware to meshtastic.github.io
uses: peaceiris/actions-gh-pages@v4
env:
# On event/* branches, use the event name as the destination prefix
DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
with:
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
external_repository: meshtastic/meshtastic.github.io
publish_branch: master
publish_dir: ./publish
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
keep_files: true
user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com
commit_message: ${{ needs.version.outputs.long }}
enable_jekyll: true

View File

@@ -21,10 +21,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
series: series:
- jammy # 22.04 - jammy # 22.04 LTS
- noble # 24.04 - noble # 24.04 LTS
- plucky # 25.04 - plucky # 25.04
# - questing # 25.10 - questing # 25.10
uses: ./.github/workflows/package_ppa.yml uses: ./.github/workflows/package_ppa.yml
with: with:
ppa_repo: |- ppa_repo: |-

View File

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

View File

@@ -10,5 +10,10 @@
}, },
"[powershell]": { "[powershell]": {
"editor.defaultFormatter": "ms-vscode.powershell" "editor.defaultFormatter": "ms-vscode.powershell"
},
"files.associations": {
"deque": "cpp",
"string": "cpp",
"vector": "cpp"
} }
} }

View File

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

View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
PYTHON=${PYTHON:-$(which python3 python | head -n 1)} PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
BPS_RESET=false BPS_RESET=false

View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
PYTHON=${PYTHON:-$(which python3 python|head -n 1)} PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
CHANGE_MODE=false CHANGE_MODE=false

View File

@@ -87,6 +87,9 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="2.7.10" date="2025-09-18">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.10</url>
</release>
<release version="2.7.9" date="2025-09-03"> <release version="2.7.9" date="2025-09-03">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9</url> <url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9</url>
</release> </release>

View File

@@ -6,6 +6,8 @@ from os.path import join
import subprocess import subprocess
import json import json
import re import re
import time
from datetime import datetime
from readprops import readProps from readprops import readProps
@@ -125,11 +127,16 @@ for pref in userPrefs:
pref_flags.append("-D" + pref + "=" + env.StringifyMacro(userPrefs[pref]) + "") pref_flags.append("-D" + pref + "=" + env.StringifyMacro(userPrefs[pref]) + "")
# General options that are passed to the C and C++ compilers # General options that are passed to the C and C++ compilers
# Calculate unix epoch for current day (midnight)
current_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
build_epoch = int(current_date.timestamp())
flags = [ flags = [
"-DAPP_VERSION=" + verObj["long"], "-DAPP_VERSION=" + verObj["long"],
"-DAPP_VERSION_SHORT=" + verObj["short"], "-DAPP_VERSION_SHORT=" + verObj["short"],
"-DAPP_ENV=" + env.get("PIOENV"), "-DAPP_ENV=" + env.get("PIOENV"),
"-DAPP_REPO=" + repo_owner, "-DAPP_REPO=" + repo_owner,
"-DBUILD_EPOCH=" + str(build_epoch),
] + pref_flags ] + pref_flags
print ("Using flags:") print ("Using flags:")

50
debian/changelog vendored
View File

@@ -1,50 +1,6 @@
meshtasticd (2.7.9.0) UNRELEASED; urgency=medium meshtasticd (2.7.10.0) UNRELEASED; urgency=medium
[ Austin Lane ]
* Initial packaging * Initial packaging
* GitHub Actions Automatic version bump * Version 2.5.19
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
[ ] -- Austin Lane <vidplace7@gmail.com> Thu, 02 Jan 2025 12:00:00 +0000
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ Ubuntu ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Wed, 03 Sep 2025 23:39:17 +0000

View File

@@ -53,14 +53,15 @@ build_flags = -Wno-missing-field-initializers
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1 -DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage -D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
#-DBUILD_EPOCH=$UNIX_TIME #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
#-D OLED_PL=1 #-D OLED_PL=1
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = direct monitor_filters = direct
lib_deps = lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master # renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/9573abb64dc9c94f3051348f2bf4fc5cedf03c22.zip https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master # renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master # renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
@@ -118,13 +119,13 @@ lib_deps =
[device-ui_base] [device-ui_base]
lib_deps = lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/3677476c8a823ee85056b5fb1d146a3e193f8276.zip https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip
; Common libs for environmental measurements in telemetry module ; Common libs for environmental measurements in telemetry module
[environmental_base] [environmental_base]
lib_deps = lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO # renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
adafruit/Adafruit BusIO@1.17.2 adafruit/Adafruit BusIO@1.17.3
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor # renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15 adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library # renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library

View File

@@ -183,9 +183,9 @@ class AmbientLightingThread : public concurrency::OSThread
#endif #endif
#endif #endif
pixels.show(); pixels.show();
LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d", // LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, // moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); // moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif #endif
#ifdef RGBLED_CA #ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red); analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);

View File

@@ -146,7 +146,7 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
{ {
int result; int result;
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
bool utf = !settingsMap[ascii_logs]; bool utf = !portduino_config.ascii_logs;
#else #else
bool utf = true; bool utf = true;
#endif #endif

View File

@@ -2,6 +2,12 @@
#include "configuration.h" #include "configuration.h"
// Forward declarations
#if defined(DEBUG_HEAP)
class MemGet;
extern MemGet memGet;
#endif
// DEBUG LED // DEBUG LED
#ifndef LED_STATE_ON #ifndef LED_STATE_ON
#define LED_STATE_ON 1 #define LED_STATE_ON 1
@@ -23,6 +29,7 @@
#define MESHTASTIC_LOG_LEVEL_ERROR "ERROR" #define MESHTASTIC_LOG_LEVEL_ERROR "ERROR"
#define MESHTASTIC_LOG_LEVEL_CRIT "CRIT " #define MESHTASTIC_LOG_LEVEL_CRIT "CRIT "
#define MESHTASTIC_LOG_LEVEL_TRACE "TRACE" #define MESHTASTIC_LOG_LEVEL_TRACE "TRACE"
#define MESHTASTIC_LOG_LEVEL_HEAP "HEAP"
#include "SerialConsole.h" #include "SerialConsole.h"
@@ -62,6 +69,25 @@
#endif #endif
#endif #endif
#if defined(DEBUG_HEAP)
#define LOG_HEAP(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__)
// Macro-based heap debugging
#define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap();
#define DEBUG_HEAP_AFTER(context, ptr) \
do { \
auto heapAfter = memGet.getFreeHeap(); \
if (heapBefore != heapAfter) { \
LOG_HEAP("Alloc in %s pointer 0x%x, size: %u, free: %u", context, ptr, heapBefore - heapAfter, heapAfter); \
} \
} while (0)
#else
#define LOG_HEAP(...)
#define DEBUG_HEAP_BEFORE
#define DEBUG_HEAP_AFTER(context, ptr)
#endif
/// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic /// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic
extern "C" void logLegacy(const char *level, const char *fmt, ...); extern "C" void logLegacy(const char *level, const char *fmt, ...);

View File

@@ -38,4 +38,49 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
return useShortName ? "Custom" : "Invalid"; return useShortName ? "Custom" : "Invalid";
break; break;
} }
}
const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role role)
{
switch (role) {
case meshtastic_Config_DeviceConfig_Role_CLIENT:
return "Client";
break;
case meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE:
return "Client Mute";
break;
case meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN:
return "Client Hidden";
break;
case meshtastic_Config_DeviceConfig_Role_CLIENT_BASE:
return "Client Base";
break;
case meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND:
return "Lost and Found";
break;
case meshtastic_Config_DeviceConfig_Role_TRACKER:
return "Tracker";
break;
case meshtastic_Config_DeviceConfig_Role_SENSOR:
return "Sensor";
break;
case meshtastic_Config_DeviceConfig_Role_TAK:
return "TAK";
break;
case meshtastic_Config_DeviceConfig_Role_TAK_TRACKER:
return "TAK Tracker";
break;
case meshtastic_Config_DeviceConfig_Role_ROUTER:
return "Router";
break;
case meshtastic_Config_DeviceConfig_Role_ROUTER_LATE:
return "Router Late";
break;
case meshtastic_Config_DeviceConfig_Role_REPEATER:
return "Repeater";
break;
default:
return "Unknown";
break;
}
} }

View File

@@ -6,4 +6,5 @@ class DisplayFormatters
public: public:
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName, static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
bool usePreset); bool usePreset);
static const char *getDeviceRole(meshtastic_Config_DeviceConfig_Role role);
}; };

View File

@@ -22,6 +22,9 @@ class GPSStatus : public Status
meshtastic_Position p = meshtastic_Position_init_default; meshtastic_Position p = meshtastic_Position_init_default;
/// Time of last valid GPS fix (millis since boot)
uint32_t lastFixMillis = 0;
public: public:
GPSStatus() { statusType = STATUS_TYPE_GPS; } GPSStatus() { statusType = STATUS_TYPE_GPS; }
@@ -83,6 +86,9 @@ class GPSStatus : public Status
uint32_t getNumSatellites() const { return p.sats_in_view; } uint32_t getNumSatellites() const { return p.sats_in_view; }
/// Return millis() when the last GPS fix occurred (0 = never)
uint32_t getLastFixMillis() const { return lastFixMillis; }
bool matches(const GPSStatus *newStatus) const bool matches(const GPSStatus *newStatus) const
{ {
#ifdef GPS_DEBUG #ifdef GPS_DEBUG
@@ -114,6 +120,9 @@ class GPSStatus : public Status
if (isDirty) { if (isDirty) {
if (hasLock) { if (hasLock) {
// Record time of last valid GPS fix
lastFixMillis = millis();
// In debug logs, identify position by @timestamp:stage (stage 3 = notify) // In debug logs, identify position by @timestamp:stage (stage 3 = notify)
LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d", p.timestamp, LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d", p.timestamp,
p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5, p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5,

View File

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

View File

@@ -4,6 +4,7 @@
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#include "configuration.h" #include "configuration.h"
#include "main.h" #include "main.h"
#include "memGet.h"
#include "mesh/generated/meshtastic/mesh.pb.h" #include "mesh/generated/meshtastic/mesh.pb.h"
#include <assert.h> #include <assert.h>
#include <cstring> #include <cstring>
@@ -57,7 +58,7 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
#endif #endif
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
bool color = !settingsMap[ascii_logs]; bool color = !portduino_config.ascii_logs;
#else #else
bool color = true; bool color = true;
#endif #endif
@@ -99,7 +100,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
size_t r = 0; size_t r = 0;
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
bool color = !settingsMap[ascii_logs]; bool color = !portduino_config.ascii_logs;
#else #else
bool color = true; bool color = true;
#endif #endif
@@ -166,6 +167,16 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
print(thread->ThreadName); print(thread->ThreadName);
print("] "); print("] ");
} }
#ifdef DEBUG_HEAP
// Add heap free space bytes prefix before every log message
#ifdef ARCH_PORTDUINO
::printf("[heap %u] ", memGet.getFreeHeap());
#else
printf("[heap %u] ", memGet.getFreeHeap());
#endif
#endif // DEBUG_HEAP
r += vprintf(logLevel, format, arg); r += vprintf(logLevel, format, arg);
} }
@@ -288,7 +299,7 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
// level trace is special, two possible ways to handle it. // level trace is special, two possible ways to handle it.
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
if (settingsStrings[traceFilename] != "") { if (portduino_config.traceFilename != "") {
va_list arg; va_list arg;
va_start(arg, format); va_start(arg, format);
try { try {
@@ -297,18 +308,18 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
} }
va_end(arg); va_end(arg);
} }
if (settingsMap[logoutputlevel] < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { if (portduino_config.logoutputlevel < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
delete[] newFormat; delete[] newFormat;
return; return;
} }
} }
if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { if (portduino_config.logoutputlevel < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
delete[] newFormat; delete[] newFormat;
return; return;
} else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) { } else if (portduino_config.logoutputlevel < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) {
delete[] newFormat; delete[] newFormat;
return; return;
} else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) { } else if (portduino_config.logoutputlevel < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) {
delete[] newFormat; delete[] newFormat;
return; return;
} }

View File

@@ -86,9 +86,9 @@ void OSThread::run()
#ifdef DEBUG_HEAP #ifdef DEBUG_HEAP
auto newHeap = memGet.getFreeHeap(); auto newHeap = memGet.getFreeHeap();
if (newHeap < heap) if (newHeap < heap)
LOG_DEBUG("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap); LOG_HEAP("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap);
if (heap < newHeap) if (heap < newHeap)
LOG_DEBUG("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap); LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
#endif #endif
runned(); runned();

View File

@@ -262,6 +262,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define VEXT_ON_VALUE LOW #define VEXT_ON_VALUE LOW
#endif #endif
// -----------------------------------------------------------------------------
// Rotary encoder
// -----------------------------------------------------------------------------
#ifndef ROTARY_DELAY
#define ROTARY_DELAY 5
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// GPS // GPS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------

View File

@@ -294,6 +294,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = AHT10; type = AHT10;
break; break;
#endif #endif
#if !defined(M5STACK_UNITC6L)
case INA_ADDR: case INA_ADDR:
case INA_ADDR_ALTERNATE: case INA_ADDR_ALTERNATE:
case INA_ADDR_WAVESHARE_UPS: case INA_ADDR_WAVESHARE_UPS:
@@ -340,6 +341,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
// else: probably a RAK12500/UBLOX GPS on I2C // else: probably a RAK12500/UBLOX GPS on I2C
} }
break; break;
#endif
case MCP9808_ADDR: case MCP9808_ADDR:
// We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some // We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some
// weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips. // weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips.

View File

@@ -1,5 +1,4 @@
#include <cstring> // Include for strstr #include <cstring> // Include for strstr
#include <string>
#include <vector> #include <vector>
#include "configuration.h" #include "configuration.h"
@@ -517,6 +516,7 @@ bool GPS::setup()
} }
} }
// Rare Serial Speeds // Rare Serial Speeds
#ifndef CONFIG_IDF_TARGET_ESP32C6
if (probeTries == GPS_PROBETRIES) { if (probeTries == GPS_PROBETRIES) {
LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]); LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]);
gnssModel = probe(rareSerialSpeeds[speedSelect]); gnssModel = probe(rareSerialSpeeds[speedSelect]);
@@ -527,6 +527,7 @@ bool GPS::setup()
} }
} }
} }
#endif
} }
if (gnssModel != GNSS_MODEL_UNKNOWN) { if (gnssModel != GNSS_MODEL_UNKNOWN) {
@@ -808,6 +809,14 @@ bool GPS::setup()
} else { } else {
LOG_INFO("GNSS module configuration saved!"); LOG_INFO("GNSS module configuration saved!");
} }
} else if (gnssModel == GNSS_MODEL_CM121) {
// only ask for RMC and GGA
// enable GGA
_serial_gps->write("$CFGMSG,0,0,1,1*1B\r\n");
delay(250);
// enable RMC
_serial_gps->write("$CFGMSG,0,4,1,1*1F\r\n");
delay(250);
} }
didSerialInit = true; didSerialInit = true;
} }
@@ -1206,7 +1215,7 @@ static const char *DETECTED_MESSAGE = "%s detected";
LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \ LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \
clearBuffer(); \ clearBuffer(); \
_serial_gps->write(COMMAND "\r\n"); \ _serial_gps->write(COMMAND "\r\n"); \
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP); \ GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP, serialSpeed); \
if (detectedDriver != GNSS_MODEL_UNKNOWN) { \ if (detectedDriver != GNSS_MODEL_UNKNOWN) { \
return detectedDriver; \ return detectedDriver; \
} \ } \
@@ -1240,9 +1249,15 @@ GnssModel_t GPS::probe(int serialSpeed)
_serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n"); _serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
_serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n"); _serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
delay(20); delay(20);
// Close NMEA sequences on CM121
_serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n");
_serial_gps->write("$CFGMSG,0,2,0,1*18\r\n");
_serial_gps->write("$CFGMSG,0,3,0,1*19\r\n");
delay(20);
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
std::vector<ChipInfo> unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}}; std::vector<ChipInfo> unicore = {
{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500); PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
std::vector<ChipInfo> atgm = { std::vector<ChipInfo> atgm = {
@@ -1368,36 +1383,55 @@ GnssModel_t GPS::probe(int serialSpeed)
return GNSS_MODEL_UNKNOWN; return GNSS_MODEL_UNKNOWN;
} }
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap) GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap, int serialSpeed)
{ {
String response = ""; // Calculate buffer size based on baud rate - 256 bytes for 9600 baud as baseline
// Higher baud rates get proportionally larger buffers to handle more data
int bufferSize = (serialSpeed * 256) / 9600;
// Clamp buffer size between reasonable limits
if (bufferSize < 128)
bufferSize = 128;
if (bufferSize > 2048)
bufferSize = 2048;
char *response = new char[bufferSize](); // Dynamically allocate based on baud rate
uint16_t responseLen = 0;
unsigned long start = millis(); unsigned long start = millis();
while (millis() - start < timeout) { while (millis() - start < timeout) {
if (_serial_gps->available()) { if (_serial_gps->available()) {
response += (char)_serial_gps->read(); char c = _serial_gps->read();
if (response.endsWith(",") || response.endsWith("\r\n")) { // Add char to buffer if there's space
if (responseLen < bufferSize - 1) {
response[responseLen++] = c;
response[responseLen] = '\0';
}
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
#ifdef GPS_DEBUG #ifdef GPS_DEBUG
LOG_DEBUG(response.c_str()); LOG_DEBUG(response);
#endif #endif
// check if we can see our chips // check if we can see our chips
for (const auto &chipInfo : responseMap) { for (const auto &chipInfo : responseMap) {
if (strstr(response.c_str(), chipInfo.detectionString.c_str()) != nullptr) { if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
LOG_INFO("%s detected", chipInfo.chipName.c_str()); LOG_INFO("%s detected", chipInfo.chipName.c_str());
delete[] response; // Cleanup before return
return chipInfo.driver; return chipInfo.driver;
} }
} }
} }
if (response.endsWith("\r\n")) { if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
response.trim(); // Reset the response buffer for the next potential message
response = ""; // Reset the response string for the next potential message responseLen = 0;
response[0] = '\0';
} }
} }
} }
#ifdef GPS_DEBUG #ifdef GPS_DEBUG
LOG_DEBUG(response.c_str()); LOG_DEBUG(response);
#endif #endif
return GNSS_MODEL_UNKNOWN; // Return empty string on timeout delete[] response; // Cleanup before return
return GNSS_MODEL_UNKNOWN; // Return unknown on timeout
} }
GPS *GPS::createGps() GPS *GPS::createGps()
@@ -1422,7 +1456,7 @@ GPS *GPS::createGps()
_en_gpio = PIN_GPS_EN; _en_gpio = PIN_GPS_EN;
#endif #endif
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
if (!settingsMap[has_gps]) if (!portduino_config.has_gps)
return nullptr; return nullptr;
#endif #endif
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all

View File

@@ -31,7 +31,8 @@ typedef enum {
GNSS_MODEL_MTK_PA1616S, GNSS_MODEL_MTK_PA1616S,
GNSS_MODEL_AG3335, GNSS_MODEL_AG3335,
GNSS_MODEL_AG3352, GNSS_MODEL_AG3352,
GNSS_MODEL_LS20031 GNSS_MODEL_LS20031,
GNSS_MODEL_CM121
} GnssModel_t; } GnssModel_t;
typedef enum { typedef enum {
@@ -236,7 +237,7 @@ class GPS : private concurrency::OSThread
virtual int32_t runOnce() override; virtual int32_t runOnce() override;
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap); GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap, int serialSpeed);
// Get GNSS model // Get GNSS model
GnssModel_t probe(int serialSpeed); GnssModel_t probe(int serialSpeed);

View File

@@ -9,6 +9,9 @@
static RTCQuality currentQuality = RTCQualityNone; static RTCQuality currentQuality = RTCQualityNone;
uint32_t lastSetFromPhoneNtpOrGps = 0; uint32_t lastSetFromPhoneNtpOrGps = 0;
static uint32_t lastTimeValidationWarning = 0;
static const uint32_t TIME_VALIDATION_WARNING_INTERVAL_MS = 15000; // 15 seconds
RTCQuality getRTCQuality() RTCQuality getRTCQuality()
{ {
return currentQuality; return currentQuality;
@@ -48,7 +51,9 @@ RTCSetResult readFromRTC()
#ifdef BUILD_EPOCH #ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) { if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
}
return RTCSetResultInvalidTime; return RTCSetResultInvalidTime;
} }
#endif #endif
@@ -87,7 +92,10 @@ RTCSetResult readFromRTC()
#ifdef BUILD_EPOCH #ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) { if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime; return RTCSetResultInvalidTime;
} }
#endif #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 uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH #ifdef BUILD_EPOCH
if (tv->tv_sec < BUILD_EPOCH) { if (tv->tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime; return RTCSetResultInvalidTime;
} else if (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) { } else if ((uint64_t)tv->tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) {
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH, if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
BUILD_EPOCH + FORTY_YEARS); // Calculate max allowed time safely to avoid overflow in logging
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime; return RTCSetResultInvalidTime;
} }
#endif #endif
@@ -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 uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH #ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) { if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime; return RTCSetResultInvalidTime;
} else if (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) { } else if ((uint64_t)tv.tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) {
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH, if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
BUILD_EPOCH + FORTY_YEARS); // Calculate max allowed time safely to avoid overflow in logging
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime; return RTCSetResultInvalidTime;
} }
#endif #endif
@@ -316,14 +342,40 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
time_t gm_mktime(struct tm *tm) time_t gm_mktime(struct tm *tm)
{ {
#if !MESHTASTIC_EXCLUDE_TZ #if !MESHTASTIC_EXCLUDE_TZ
setenv("TZ", "GMT0", 1); time_t result = 0;
time_t res = mktime(tm);
if (*config.device.tzdef) { // First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
setenv("TZ", config.device.tzdef, 1); int year = 1900 + tm->tm_year; // tm_year is years since 1900
} else { int year_minus_one = year - 1;
setenv("TZ", "UTC0", 1); int days_before_this_year = 0;
days_before_this_year += year_minus_one * 365;
// leap days: every 4 years, except 100s, but including 400s.
days_before_this_year += year_minus_one / 4 - year_minus_one / 100 + year_minus_one / 400;
// subtract from 1970-01-01 to get days since epoch
days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400);
// Now, within this tm->year, compute the days *before* this tm->month starts.
int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11
// If this is a leap year, and we're past February, add a day:
if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
days_this_year_before_this_month += 1;
} }
return res;
// And within this month:
int days_this_month_before_today = tm->tm_mday - 1; // tm->tm_mday is 1..31
// Now combine them all together, and convert days to seconds:
result += (days_before_this_year + days_this_year_before_this_month + days_this_month_before_today);
result *= 86400L;
// Finally, add in the hours, minutes, and seconds of today:
result += tm->tm_hour * 3600;
result += tm->tm_min * 60;
result += tm->tm_sec;
return result;
#else #else
return mktime(tm); return mktime(tm);
#endif #endif

View File

@@ -56,5 +56,5 @@ time_t gm_mktime(struct tm *tm);
#define SEC_PER_HOUR 3600 #define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60 #define SEC_PER_MIN 60
#ifdef BUILD_EPOCH #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 #endif

View File

@@ -25,6 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "PowerMon.h" #include "PowerMon.h"
#include "Throttle.h" #include "Throttle.h"
#include "configuration.h" #include "configuration.h"
#include "meshUtils.h"
#if HAS_SCREEN #if HAS_SCREEN
#include <OLEDDisplay.h> #include <OLEDDisplay.h>
@@ -58,7 +59,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "mesh-pb-constants.h" #include "mesh-pb-constants.h"
#include "mesh/Channels.h" #include "mesh/Channels.h"
#include "mesh/generated/meshtastic/deviceonly.pb.h" #include "mesh/generated/meshtastic/deviceonly.pb.h"
#include "meshUtils.h"
#include "modules/ExternalNotificationModule.h" #include "modules/ExternalNotificationModule.h"
#include "modules/TextMessageModule.h" #include "modules/TextMessageModule.h"
#include "modules/WaypointModule.h" #include "modules/WaypointModule.h"
@@ -317,6 +317,14 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#elif defined(USE_SSD1306) #elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry, dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_SPISSD1306)
dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48);
if (!dispdev->init()) {
LOG_DEBUG("Error: SSD1306 not detected!");
} else {
static_cast<SSD1306Spi *>(dispdev)->setHorizontalOffset(32);
LOG_INFO("SSD1306 init success");
}
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
dispdev = new TFTDisplay(address.address, -1, -1, geometry, dispdev = new TFTDisplay(address.address, -1, -1, geometry,
@@ -332,7 +340,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif ARCH_PORTDUINO #elif ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
if (settingsMap[displayPanel] != no_screen) { if (portduino_config.displayPanel != no_screen) {
LOG_DEBUG("Make TFTDisplay!"); LOG_DEBUG("Make TFTDisplay!");
dispdev = new TFTDisplay(address.address, -1, -1, geometry, dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
@@ -507,7 +515,7 @@ void Screen::setup()
// === Apply loaded brightness === // === Apply loaded brightness ===
#if defined(ST7789_CS) #if defined(ST7789_CS)
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness); static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) #elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306)
dispdev->setBrightness(brightness); dispdev->setBrightness(brightness);
#endif #endif
LOG_INFO("Applied screen brightness: %d", brightness); LOG_INFO("Applied screen brightness: %d", brightness);
@@ -554,7 +562,7 @@ void Screen::setup()
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically(); static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
#elif defined(USE_ST7789) #elif defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically(); static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
#else #elif !defined(M5STACK_UNITC6L)
dispdev->flipScreenVertically(); dispdev->flipScreenVertically();
#endif #endif
} }
@@ -580,7 +588,7 @@ void Screen::setup()
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
if (settingsMap[touchscreenModule]) { if (portduino_config.touchscreenModule) {
touchScreenImpl1 = touchScreenImpl1 =
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch); new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
touchScreenImpl1->init(); touchScreenImpl1->init();
@@ -692,7 +700,11 @@ int32_t Screen::runOnce()
#ifndef DISABLE_WELCOME_UNSET #ifndef DISABLE_WELCOME_UNSET
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
#if defined(M5STACK_UNITC6L)
menuHandler::LoraRegionPicker();
#else
menuHandler::OnboardMessage(); menuHandler::OnboardMessage();
#endif
} }
#endif #endif
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) { if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
@@ -889,71 +901,95 @@ void Screen::setFrames(FrameFocus focus)
} }
#if defined(DISPLAY_CLOCK_FRAME) #if defined(DISPLAY_CLOCK_FRAME)
fsi.positions.clock = numframes; if (!hiddenFrames.clock) {
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame fsi.positions.clock = numframes;
: graphics::ClockRenderer::drawDigitalClockFrame; #if defined(M5STACK_UNITC6L)
indicatorIcons.push_back(digital_icon_clock); 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 #endif
// Declare this early so its available in FOCUS_PRESERVE block // Declare this early so its available in FOCUS_PRESERVE block
bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message); bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
fsi.positions.home = numframes; if (!hiddenFrames.home) {
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused; fsi.positions.home = numframes;
indicatorIcons.push_back(icon_home); normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
indicatorIcons.push_back(icon_home);
}
fsi.positions.textMessage = numframes; fsi.positions.textMessage = numframes;
normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame; normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
indicatorIcons.push_back(icon_mail); indicatorIcons.push_back(icon_mail);
#ifndef USE_EINK #ifndef USE_EINK
fsi.positions.nodelist = numframes; if (!hiddenFrames.nodelist) {
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen; fsi.positions.nodelist = numframes;
indicatorIcons.push_back(icon_nodes); normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
indicatorIcons.push_back(icon_nodes);
}
#endif #endif
// Show detailed node views only on E-Ink builds // Show detailed node views only on E-Ink builds
#ifdef USE_EINK #ifdef USE_EINK
fsi.positions.nodelist_lastheard = numframes; if (!hiddenFrames.nodelist_lastheard) {
normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen; fsi.positions.nodelist_lastheard = numframes;
indicatorIcons.push_back(icon_nodes); normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
indicatorIcons.push_back(icon_nodes);
fsi.positions.nodelist_hopsignal = numframes; }
normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen; if (!hiddenFrames.nodelist_hopsignal) {
indicatorIcons.push_back(icon_signal); fsi.positions.nodelist_hopsignal = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
fsi.positions.nodelist_distance = numframes; indicatorIcons.push_back(icon_signal);
normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen; }
indicatorIcons.push_back(icon_distance); if (!hiddenFrames.nodelist_distance) {
fsi.positions.nodelist_distance = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
indicatorIcons.push_back(icon_distance);
}
#endif #endif
#if HAS_GPS #if HAS_GPS
fsi.positions.nodelist_bearings = numframes; if (!hiddenFrames.nodelist_bearings) {
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses; fsi.positions.nodelist_bearings = numframes;
indicatorIcons.push_back(icon_list); normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
indicatorIcons.push_back(icon_list);
fsi.positions.gps = numframes; }
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen; if (!hiddenFrames.gps) {
indicatorIcons.push_back(icon_compass); fsi.positions.gps = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
indicatorIcons.push_back(icon_compass);
}
#endif #endif
if (RadioLibInterface::instance) { if (RadioLibInterface::instance && !hiddenFrames.lora) {
fsi.positions.lora = numframes; fsi.positions.lora = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused; normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
indicatorIcons.push_back(icon_radio); indicatorIcons.push_back(icon_radio);
} }
if (!dismissedFrames.memory) { if (!hiddenFrames.system) {
fsi.positions.memory = numframes; fsi.positions.system = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawMemoryUsage; normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen;
indicatorIcons.push_back(icon_memory); indicatorIcons.push_back(icon_system);
} }
#if !defined(DISPLAY_CLOCK_FRAME) #if !defined(DISPLAY_CLOCK_FRAME)
fsi.positions.clock = numframes; if (!hiddenFrames.clock) {
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame fsi.positions.clock = numframes;
: graphics::ClockRenderer::drawDigitalClockFrame; normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
indicatorIcons.push_back(digital_icon_clock); : graphics::ClockRenderer::drawDigitalClockFrame;
indicatorIcons.push_back(digital_icon_clock);
}
#endif #endif
if (!hiddenFrames.chirpy) {
fsi.positions.chirpy = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy;
indicatorIcons.push_back(small_chirpy);
}
#if HAS_WIFI && !defined(ARCH_PORTDUINO) #if HAS_WIFI && !defined(ARCH_PORTDUINO)
if (!dismissedFrames.wifi && isWifiAvailable()) { if (!hiddenFrames.wifi && isWifiAvailable()) {
fsi.positions.wifi = numframes; fsi.positions.wifi = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline; normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
indicatorIcons.push_back(icon_wifi); indicatorIcons.push_back(icon_wifi);
@@ -995,27 +1031,29 @@ void Screen::setFrames(FrameFocus focus)
if (numMeshNodes > 0) if (numMeshNodes > 0)
numMeshNodes--; numMeshNodes--;
// Temporary array to hold favorite node frames if (!hiddenFrames.show_favorites) {
std::vector<FrameCallback> favoriteFrames; // Temporary array to hold favorite node frames
std::vector<FrameCallback> favoriteFrames;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) { if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo); favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
}
} }
}
// Insert favorite frames *after* collecting them all // Insert favorite frames *after* collecting them all
if (!favoriteFrames.empty()) { if (!favoriteFrames.empty()) {
fsi.positions.firstFavorite = numframes; fsi.positions.firstFavorite = numframes;
for (const auto &f : favoriteFrames) { for (const auto &f : favoriteFrames) {
normalFrames[numframes++] = f; normalFrames[numframes++] = f;
indicatorIcons.push_back(icon_node); indicatorIcons.push_back(icon_node);
}
fsi.positions.lastFavorite = numframes - 1;
} else {
fsi.positions.firstFavorite = 255;
fsi.positions.lastFavorite = 255;
} }
fsi.positions.lastFavorite = numframes - 1;
} else {
fsi.positions.firstFavorite = 255;
fsi.positions.lastFavorite = 255;
} }
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
@@ -1054,7 +1092,7 @@ void Screen::setFrames(FrameFocus focus)
ui->switchToFrame(fsi.positions.clock); ui->switchToFrame(fsi.positions.clock);
break; break;
case FOCUS_SYSTEM: case FOCUS_SYSTEM:
ui->switchToFrame(fsi.positions.memory); ui->switchToFrame(fsi.positions.system);
break; break;
case FOCUS_PRESERVE: case FOCUS_PRESERVE:
@@ -1082,30 +1120,101 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
setFastFramerate(); setFastFramerate();
} }
void Screen::toggleFrameVisibility(const std::string &frameName)
{
#ifndef USE_EINK
if (frameName == "nodelist") {
hiddenFrames.nodelist = !hiddenFrames.nodelist;
}
#endif
#ifdef USE_EINK
if (frameName == "nodelist_lastheard") {
hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard;
}
if (frameName == "nodelist_hopsignal") {
hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal;
}
if (frameName == "nodelist_distance") {
hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance;
}
#endif
#if HAS_GPS
if (frameName == "nodelist_bearings") {
hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
}
if (frameName == "gps") {
hiddenFrames.gps = !hiddenFrames.gps;
}
#endif
if (frameName == "lora") {
hiddenFrames.lora = !hiddenFrames.lora;
}
if (frameName == "clock") {
hiddenFrames.clock = !hiddenFrames.clock;
}
if (frameName == "show_favorites") {
hiddenFrames.show_favorites = !hiddenFrames.show_favorites;
}
if (frameName == "chirpy") {
hiddenFrames.chirpy = !hiddenFrames.chirpy;
}
}
bool Screen::isFrameHidden(const std::string &frameName) const
{
#ifndef USE_EINK
if (frameName == "nodelist")
return hiddenFrames.nodelist;
#endif
#ifdef USE_EINK
if (frameName == "nodelist_lastheard")
return hiddenFrames.nodelist_lastheard;
if (frameName == "nodelist_hopsignal")
return hiddenFrames.nodelist_hopsignal;
if (frameName == "nodelist_distance")
return hiddenFrames.nodelist_distance;
#endif
#if HAS_GPS
if (frameName == "nodelist_bearings")
return hiddenFrames.nodelist_bearings;
if (frameName == "gps")
return hiddenFrames.gps;
#endif
if (frameName == "lora")
return hiddenFrames.lora;
if (frameName == "clock")
return hiddenFrames.clock;
if (frameName == "show_favorites")
return hiddenFrames.show_favorites;
if (frameName == "chirpy")
return hiddenFrames.chirpy;
return false;
}
// Dismisses the currently displayed screen frame, if possible // Dismisses the currently displayed screen frame, if possible
// Relevant for text message, waypoint, others in future? // Relevant for text message, waypoint, others in future?
// Triggered with a CardKB keycombo // Triggered with a CardKB keycombo
void Screen::dismissCurrentFrame() void Screen::hideCurrentFrame()
{ {
uint8_t currentFrame = ui->getUiState()->currentFrame; uint8_t currentFrame = ui->getUiState()->currentFrame;
bool dismissed = false; bool dismissed = false;
if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) { if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
LOG_INFO("Dismiss Text Message"); LOG_INFO("Hide Text Message");
devicestate.has_rx_text_message = false; devicestate.has_rx_text_message = false;
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
} else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) { } else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
LOG_DEBUG("Dismiss Waypoint"); LOG_DEBUG("Hide Waypoint");
devicestate.has_rx_waypoint = false; devicestate.has_rx_waypoint = false;
dismissedFrames.waypoint = true; hiddenFrames.waypoint = true;
dismissed = true; dismissed = true;
} else if (currentFrame == framesetInfo.positions.wifi) { } else if (currentFrame == framesetInfo.positions.wifi) {
LOG_DEBUG("Dismiss WiFi Screen"); LOG_DEBUG("Hide WiFi Screen");
dismissedFrames.wifi = true; hiddenFrames.wifi = true;
dismissed = true; dismissed = true;
} else if (currentFrame == framesetInfo.positions.memory) { } else if (currentFrame == framesetInfo.positions.lora) {
LOG_INFO("Dismiss Memory"); LOG_INFO("Hide LoRa");
dismissedFrames.memory = true; hiddenFrames.lora = true;
dismissed = true; dismissed = true;
} }
@@ -1226,6 +1335,10 @@ void Screen::handleShowNextFrame()
void Screen::setFastFramerate() void Screen::setFastFramerate()
{ {
#if defined(M5STACK_UNITC6L)
dispdev->clear();
dispdev->display();
#endif
// We are about to start a transition so speed up fps // We are about to start a transition so speed up fps
targetFramerate = SCREEN_TRANSITION_FRAMERATE; targetFramerate = SCREEN_TRANSITION_FRAMERATE;
@@ -1257,7 +1370,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
// Outgoing message (likely sent from phone) // Outgoing message (likely sent from phone)
devicestate.has_rx_text_message = false; devicestate.has_rx_text_message = false;
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
dismissedFrames.textMessage = true; hiddenFrames.textMessage = true;
hasUnreadMessage = false; // Clear unread state when user replies hasUnreadMessage = false; // Clear unread state when user replies
setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
@@ -1297,13 +1410,23 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
} }
} else { } else {
if (longName && longName[0]) { if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName); snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
#endif
} else { } else {
strcpy(banner, "New Message"); strcpy(banner, "New Message");
} }
} }
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
playLongBeep();
#else
screen->showSimpleBanner(banner, 3000); screen->showSimpleBanner(banner, 3000);
#endif
} }
} }
@@ -1372,7 +1495,7 @@ int Screen::handleInputEvent(const InputEvent *event)
} else if (event->inputEvent == INPUT_BROKER_SELECT) { } else if (event->inputEvent == INPUT_BROKER_SELECT) {
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) { if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
menuHandler::homeBaseMenu(); menuHandler::homeBaseMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.memory) { } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) {
menuHandler::systemBaseMenu(); menuHandler::systemBaseMenu();
#if HAS_GPS #if HAS_GPS
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) { } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
@@ -1381,12 +1504,16 @@ int Screen::handleInputEvent(const InputEvent *event)
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) { } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
menuHandler::clockMenu(); menuHandler::clockMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) { } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
menuHandler::LoraRegionPicker(); menuHandler::loraMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) { } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
if (devicestate.rx_text_message.from) { if (devicestate.rx_text_message.from) {
menuHandler::messageResponseMenu(); menuHandler::messageResponseMenu();
} else { } else {
#if defined(M5STACK_UNITC6L)
menuHandler::textMessageMenu();
#else
menuHandler::textMessageBaseMenu(); menuHandler::textMessageBaseMenu();
#endif
} }
} else if (framesetInfo.positions.firstFavorite != 255 && } else if (framesetInfo.positions.firstFavorite != 255 &&
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite && this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
@@ -1445,13 +1572,15 @@ bool shouldWakeOnReceivedMessage()
/* /*
The goal here is to determine when we do NOT wake up the screen on message received: The goal here is to determine when we do NOT wake up the screen on message received:
- Any ext. notifications are turned on - Any ext. notifications are turned on
- If role is not client / client_mute - If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE
- If the battery level is very low - If the battery level is very low
*/ */
if (moduleConfig.external_notification.enabled) { if (moduleConfig.external_notification.enabled) {
return false; return false;
} }
if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) { if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT,
meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN,
meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
return false; return false;
} }
if (powerStatus && powerStatus->getBatteryChargePercent() < 10) { if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {

View File

@@ -81,6 +81,8 @@ class Screen
#include <SSD1306Wire.h> #include <SSD1306Wire.h>
#elif defined(USE_ST7789) #elif defined(USE_ST7789)
#include <ST7789Spi.h> #include <ST7789Spi.h>
#elif defined(USE_SPISSD1306)
#include <SSD1306Spi.h>
#else #else
// the SH1106/SSD1306 variant is auto-detected // the SH1106/SSD1306 variant is auto-detected
#include <AutoOLEDWire.h> #include <AutoOLEDWire.h>
@@ -591,7 +593,11 @@ class Screen : public concurrency::OSThread
void setSSLFrames(); void setSSLFrames();
// Dismiss the currently focussed frame, if possible (e.g. text message, waypoint) // Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
void dismissCurrentFrame(); void hideCurrentFrame();
// Menu-driven Show / Hide Toggle
void toggleFrameVisibility(const std::string &frameName);
bool isFrameHidden(const std::string &frameName) const;
#ifdef USE_EINK #ifdef USE_EINK
/// Draw an image to remain on E-Ink display after screen off /// Draw an image to remain on E-Ink display after screen off
@@ -653,7 +659,7 @@ class Screen : public concurrency::OSThread
uint8_t settings = 255; uint8_t settings = 255;
uint8_t wifi = 255; uint8_t wifi = 255;
uint8_t deviceFocused = 255; uint8_t deviceFocused = 255;
uint8_t memory = 255; uint8_t system = 255;
uint8_t gps = 255; uint8_t gps = 255;
uint8_t home = 255; uint8_t home = 255;
uint8_t textMessage = 255; uint8_t textMessage = 255;
@@ -663,6 +669,7 @@ class Screen : public concurrency::OSThread
uint8_t nodelist_distance = 255; uint8_t nodelist_distance = 255;
uint8_t nodelist_bearings = 255; uint8_t nodelist_bearings = 255;
uint8_t clock = 255; uint8_t clock = 255;
uint8_t chirpy = 255;
uint8_t firstFavorite = 255; uint8_t firstFavorite = 255;
uint8_t lastFavorite = 255; uint8_t lastFavorite = 255;
uint8_t lora = 255; uint8_t lora = 255;
@@ -671,12 +678,29 @@ class Screen : public concurrency::OSThread
uint8_t frameCount = 0; uint8_t frameCount = 0;
} framesetInfo; } framesetInfo;
struct DismissedFrames { struct hiddenFrames {
bool textMessage = false; bool textMessage = false;
bool waypoint = false; bool waypoint = false;
bool wifi = false; bool wifi = false;
bool memory = false; bool system = false;
} dismissedFrames; bool home = false;
bool clock = false;
#ifndef USE_EINK
bool nodelist = false;
#endif
#ifdef USE_EINK
bool nodelist_lastheard = false;
bool nodelist_hopsignal = false;
bool nodelist_distance = false;
#endif
#if HAS_GPS
bool nodelist_bearings = false;
bool gps = false;
#endif
bool lora = false;
bool show_favorites = false;
bool chirpy = true;
} hiddenFrames;
/// Try to start drawing ASAP /// Try to start drawing ASAP
void setFastFramerate(); void setFastFramerate();

View File

@@ -79,6 +79,10 @@
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28 #define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28 #define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#elif defined(M5STACK_UNITC6L)
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
#define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13
#define FONT_LARGE FONT_SMALL_LOCAL // Height: 13
#else #else
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13 #define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
#define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19 #define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19

View File

@@ -1,6 +1,7 @@
#include "graphics/SharedUIDisplay.h" #include "graphics/SharedUIDisplay.h"
#include "RTC.h" #include "RTC.h"
#include "graphics/ScreenFonts.h" #include "graphics/ScreenFonts.h"
#include "graphics/draw/UIRenderer.h"
#include "main.h" #include "main.h"
#include "meshtastic/config.pb.h" #include "meshtastic/config.pb.h"
#include "power.h" #include "power.h"
@@ -16,6 +17,10 @@ void determineResolution(int16_t screenheight, int16_t screenwidth)
isHighResolution = true; isHighResolution = true;
} }
if (screenwidth > 128 && screenheight <= 64) {
isHighResolution = false;
}
// Special case for Heltec Wireless Tracker v1.1 // Special case for Heltec Wireless Tracker v1.1
if (screenwidth == 160 && screenheight == 80) { if (screenwidth == 160 && screenheight == 80) {
isHighResolution = false; isHighResolution = false;
@@ -53,7 +58,7 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
// ************************* // *************************
// * Common Header Drawing * // * Common Header Drawing *
// ************************* // *************************
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only) void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert, bool show_date)
{ {
constexpr int HEADER_OFFSET_Y = 1; constexpr int HEADER_OFFSET_Y = 1;
y += HEADER_OFFSET_Y; y += HEADER_OFFSET_Y;
@@ -69,7 +74,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
const int screenW = display->getWidth(); const int screenW = display->getWidth();
const int screenH = display->getHeight(); const int screenH = display->getHeight();
if (!battery_only) { if (!force_no_invert) {
// === Inverted Header Background === // === Inverted Header Background ===
if (isInverted) { if (isInverted) {
display->setColor(BLACK); display->setColor(BLACK);
@@ -124,7 +129,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int batteryX = 1; int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1; int batteryY = HEADER_OFFSET_Y + 1;
#if !defined(M5STACK_UNITC6L)
// === Battery Icons === // === Battery Icons ===
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
batteryX += 1; batteryX += 1;
@@ -187,13 +192,28 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int timeStrWidth = display->getStringWidth("12:34"); // Default alignment int timeStrWidth = display->getStringWidth("12:34"); // Default alignment
int timeX = screenW - xOffset - timeStrWidth + 4; int timeX = screenW - xOffset - timeStrWidth + 4;
if (rtc_sec > 0 && !battery_only) { if (rtc_sec > 0) {
// === Build Time String === // === Build Time String ===
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY; long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
int hour = hms / SEC_PER_HOUR; int hour = hms / SEC_PER_HOUR;
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN; int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute); snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
// === Build Date String ===
char datetimeStr[25];
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
char dateLine[40];
if (isHighResolution) {
snprintf(dateLine, sizeof(dateLine), "%s", datetimeStr);
} else {
if (hasUnreadMessage) {
snprintf(dateLine, sizeof(dateLine), "%s", &datetimeStr[5]);
} else {
snprintf(dateLine, sizeof(dateLine), "%s", &datetimeStr[2]);
}
}
if (config.display.use_12h_clock) { if (config.display.use_12h_clock) {
bool isPM = hour >= 12; bool isPM = hour >= 12;
hour %= 12; hour %= 12;
@@ -202,7 +222,11 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
snprintf(timeStr, sizeof(timeStr), "%d:%02d%s", hour, minute, isPM ? "p" : "a"); snprintf(timeStr, sizeof(timeStr), "%d:%02d%s", hour, minute, isPM ? "p" : "a");
} }
timeStrWidth = display->getStringWidth(timeStr); if (show_date) {
timeStrWidth = display->getStringWidth(dateLine);
} else {
timeStrWidth = display->getStringWidth(timeStr);
}
timeX = screenW - xOffset - timeStrWidth + 3; timeX = screenW - xOffset - timeStrWidth + 3;
// === Show Mail or Mute Icon to the Left of Time === // === Show Mail or Mute Icon to the Left of Time ===
@@ -229,7 +253,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int iconW = 16, iconH = 12; int iconW = 16, iconH = 12;
int iconX = iconRightEdge - iconW; int iconX = iconRightEdge - iconW;
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1; int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
if (isInverted) { if (isInverted && !force_no_invert) {
display->setColor(WHITE); display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2); display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
display->setColor(BLACK); display->setColor(BLACK);
@@ -244,7 +268,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
} else { } else {
int iconX = iconRightEdge - (mail_width - 2); int iconX = iconRightEdge - (mail_width - 2);
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2; int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
if (isInverted) { if (isInverted && !force_no_invert) {
display->setColor(WHITE); display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2); display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
display->setColor(BLACK); display->setColor(BLACK);
@@ -287,10 +311,17 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
} }
} }
// === Draw Time === if (show_date) {
display->drawString(timeX, textY, timeStr); // === Draw Date ===
if (isBold) display->drawString(timeX, textY, dateLine);
display->drawString(timeX - 1, textY, timeStr); if (isBold)
display->drawString(timeX - 1, textY, dateLine);
} else {
// === Draw Time ===
display->drawString(timeX, textY, timeStr);
if (isBold)
display->drawString(timeX - 1, textY, timeStr);
}
} else { } else {
// === No Time Available: Mail/Mute Icon Moves to Far Right === // === No Time Available: Mail/Mute Icon Moves to Far Right ===
@@ -337,7 +368,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
} }
} }
} }
#endif
display->setColor(WHITE); // Reset for other UI display->setColor(WHITE); // Reset for other UI
} }

View File

@@ -49,7 +49,8 @@ void determineResolution(int16_t screenheight, int16_t screenwidth);
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r); void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
// Shared battery/time/mail header // Shared battery/time/mail header
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool battery_only = false); void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
bool show_date = false);
const int *getTextPositions(OLEDDisplay *display); const int *getTextPositions(OLEDDisplay *display);

View File

@@ -767,24 +767,24 @@ class LGFX : public lgfx::LGFX_Device
LGFX(void) LGFX(void)
{ {
if (settingsMap[displayPanel] == st7789) if (portduino_config.displayPanel == st7789)
_panel_instance = new lgfx::Panel_ST7789; _panel_instance = new lgfx::Panel_ST7789;
else if (settingsMap[displayPanel] == st7735) else if (portduino_config.displayPanel == st7735)
_panel_instance = new lgfx::Panel_ST7735; _panel_instance = new lgfx::Panel_ST7735;
else if (settingsMap[displayPanel] == st7735s) else if (portduino_config.displayPanel == st7735s)
_panel_instance = new lgfx::Panel_ST7735S; _panel_instance = new lgfx::Panel_ST7735S;
else if (settingsMap[displayPanel] == st7796) else if (portduino_config.displayPanel == st7796)
_panel_instance = new lgfx::Panel_ST7796; _panel_instance = new lgfx::Panel_ST7796;
else if (settingsMap[displayPanel] == ili9341) else if (portduino_config.displayPanel == ili9341)
_panel_instance = new lgfx::Panel_ILI9341; _panel_instance = new lgfx::Panel_ILI9341;
else if (settingsMap[displayPanel] == ili9342) else if (portduino_config.displayPanel == ili9342)
_panel_instance = new lgfx::Panel_ILI9342; _panel_instance = new lgfx::Panel_ILI9342;
else if (settingsMap[displayPanel] == ili9488) else if (portduino_config.displayPanel == ili9488)
_panel_instance = new lgfx::Panel_ILI9488; _panel_instance = new lgfx::Panel_ILI9488;
else if (settingsMap[displayPanel] == hx8357d) else if (portduino_config.displayPanel == hx8357d)
_panel_instance = new lgfx::Panel_HX8357D; _panel_instance = new lgfx::Panel_HX8357D;
#if defined(LGFX_SDL) #if defined(LGFX_SDL)
else if (settingsMap[displayPanel] == x11) { else if (portduino_config.displayPanel == x11) {
_panel_instance = new lgfx::Panel_sdl; _panel_instance = new lgfx::Panel_sdl;
} }
#endif #endif
@@ -795,61 +795,61 @@ class LGFX : public lgfx::LGFX_Device
auto buscfg = _bus_instance.config(); auto buscfg = _bus_instance.config();
buscfg.spi_mode = 0; buscfg.spi_mode = 0;
buscfg.spi_host = settingsMap[displayspidev]; buscfg.spi_host = portduino_config.display_spi_dev_int;
buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable) buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable)
_bus_instance.config(buscfg); // applies the set value to the bus. _bus_instance.config(buscfg); // applies the set value to the bus.
_panel_instance->setBus(&_bus_instance); // set the bus on the panel. _panel_instance->setBus(&_bus_instance); // set the bus on the panel.
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings. auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
LOG_DEBUG("Width: %d, Height: %d", settingsMap[displayWidth], settingsMap[displayHeight]); LOG_DEBUG("Width: %d, Height: %d", portduino_config.displayWidth, portduino_config.displayHeight);
cfg.pin_cs = settingsMap[displayCS]; // Pin number where CS is connected (-1 = disable) cfg.pin_cs = portduino_config.displayCS.pin; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = settingsMap[displayReset]; cfg.pin_rst = portduino_config.displayReset.pin;
if (settingsMap[displayRotate]) { if (portduino_config.displayRotate) {
cfg.panel_width = settingsMap[displayHeight]; // actual displayable width cfg.panel_width = portduino_config.displayHeight; // actual displayable width
cfg.panel_height = settingsMap[displayWidth]; // actual displayable height cfg.panel_height = portduino_config.displayWidth; // actual displayable height
} else { } else {
cfg.panel_width = settingsMap[displayWidth]; // actual displayable width cfg.panel_width = portduino_config.displayWidth; // actual displayable width
cfg.panel_height = settingsMap[displayHeight]; // actual displayable height cfg.panel_height = portduino_config.displayHeight; // actual displayable height
} }
cfg.offset_x = settingsMap[displayOffsetX]; // Panel offset amount in X direction cfg.offset_x = portduino_config.displayOffsetX; // Panel offset amount in X direction
cfg.offset_y = settingsMap[displayOffsetY]; // Panel offset amount in Y direction cfg.offset_y = portduino_config.displayOffsetY; // Panel offset amount in Y direction
cfg.offset_rotation = settingsMap[displayOffsetRotate]; // Rotation direction value offset 0~7 (4~7 is mirrored) cfg.offset_rotation = portduino_config.displayOffsetRotate; // Rotation direction value offset 0~7 (4~7 is mirrored)
cfg.invert = settingsMap[displayInvert]; // Set to true if the light/darkness of the panel is reversed cfg.invert = portduino_config.displayInvert; // Set to true if the light/darkness of the panel is reversed
_panel_instance->config(cfg); _panel_instance->config(cfg);
// Configure settings for touch control. // Configure settings for touch control.
if (settingsMap[touchscreenModule]) { if (portduino_config.touchscreenModule) {
if (settingsMap[touchscreenModule] == xpt2046) { if (portduino_config.touchscreenModule == xpt2046) {
_touch_instance = new lgfx::Touch_XPT2046; _touch_instance = new lgfx::Touch_XPT2046;
} else if (settingsMap[touchscreenModule] == stmpe610) { } else if (portduino_config.touchscreenModule == stmpe610) {
_touch_instance = new lgfx::Touch_STMPE610; _touch_instance = new lgfx::Touch_STMPE610;
} else if (settingsMap[touchscreenModule] == ft5x06) { } else if (portduino_config.touchscreenModule == ft5x06) {
_touch_instance = new lgfx::Touch_FT5x06; _touch_instance = new lgfx::Touch_FT5x06;
} }
auto touch_cfg = _touch_instance->config(); auto touch_cfg = _touch_instance->config();
touch_cfg.pin_cs = settingsMap[touchscreenCS]; touch_cfg.pin_cs = portduino_config.touchscreenCS.pin;
touch_cfg.x_min = 0; touch_cfg.x_min = 0;
touch_cfg.x_max = settingsMap[displayHeight] - 1; touch_cfg.x_max = portduino_config.displayHeight - 1;
touch_cfg.y_min = 0; touch_cfg.y_min = 0;
touch_cfg.y_max = settingsMap[displayWidth] - 1; touch_cfg.y_max = portduino_config.displayWidth - 1;
touch_cfg.pin_int = settingsMap[touchscreenIRQ]; touch_cfg.pin_int = portduino_config.touchscreenIRQ.pin;
touch_cfg.bus_shared = true; touch_cfg.bus_shared = true;
touch_cfg.offset_rotation = settingsMap[touchscreenRotate]; touch_cfg.offset_rotation = portduino_config.touchscreenRotate;
if (settingsMap[touchscreenI2CAddr] != -1) { if (portduino_config.touchscreenI2CAddr != -1) {
touch_cfg.i2c_addr = settingsMap[touchscreenI2CAddr]; touch_cfg.i2c_addr = portduino_config.touchscreenI2CAddr;
} else { } else {
touch_cfg.spi_host = settingsMap[touchscreenspidev]; touch_cfg.spi_host = portduino_config.touchscreen_spi_dev_int;
} }
_touch_instance->config(touch_cfg); _touch_instance->config(touch_cfg);
_panel_instance->setTouch(_touch_instance); _panel_instance->setTouch(_touch_instance);
} }
#if defined(LGFX_SDL) #if defined(LGFX_SDL)
if (settingsMap[displayPanel] == x11) { if (portduino_config.displayPanel == x11) {
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance; lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
sdl_panel_->setup(); sdl_panel_->setup();
sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER); sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER);
@@ -1115,10 +1115,10 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
backlightEnable = p; backlightEnable = p;
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (settingsMap[displayRotate]) { if (portduino_config.displayRotate) {
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayWidth], settingsMap[configNames::displayHeight]); setGeometry(GEOMETRY_RAWMODE, portduino_config.displayWidth, portduino_config.displayWidth);
} else { } else {
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]); setGeometry(GEOMETRY_RAWMODE, portduino_config.displayHeight, portduino_config.displayHeight);
} }
#elif defined(SCREEN_ROTATE) #elif defined(SCREEN_ROTATE)
@@ -1240,7 +1240,7 @@ void TFTDisplay::sdlLoop()
#if defined(LGFX_SDL) #if defined(LGFX_SDL)
static int lastPressed = 0; static int lastPressed = 0;
static int shuttingDown = false; static int shuttingDown = false;
if (settingsMap[displayPanel] == x11) { if (portduino_config.displayPanel == x11) {
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance; lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance;
if (sdl_panel_->loop() && !shuttingDown) { if (sdl_panel_->loop() && !shuttingDown) {
LOG_WARN("Window Closed!"); LOG_WARN("Window Closed!");
@@ -1288,8 +1288,8 @@ void TFTDisplay::sendCommand(uint8_t com)
backlightEnable->set(true); backlightEnable->set(true);
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
display(true); display(true);
if (settingsMap[displayBacklight] > 0) if (portduino_config.displayBacklight.pin > 0)
digitalWrite(settingsMap[displayBacklight], TFT_BACKLIGHT_ON); digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
tft->wakeup(); tft->wakeup();
tft->powerSaveOff(); tft->powerSaveOff();
@@ -1312,8 +1312,8 @@ void TFTDisplay::sendCommand(uint8_t com)
backlightEnable->set(false); backlightEnable->set(false);
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
tft->clear(); tft->clear();
if (settingsMap[displayBacklight] > 0) if (portduino_config.displayBacklight.pin > 0)
digitalWrite(settingsMap[displayBacklight], !TFT_BACKLIGHT_ON); digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
tft->sleep(); tft->sleep();
tft->powerSaveOn(); tft->powerSaveOn();

View File

@@ -190,7 +190,8 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
// === Set Title, Blank for Clock // === Set Title, Blank for Clock
const char *titleStr = ""; const char *titleStr = "";
// === Header === // === Header ===
graphics::drawCommonHeader(display, x, y, titleStr, true); graphics::drawCommonHeader(display, x, y, titleStr, true, true);
int line = 0;
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) { if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
@@ -294,6 +295,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2, display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
isPM ? "pm" : "am"); isPM ? "pm" : "am");
} }
#ifndef USE_EINK #ifndef USE_EINK
xOffset = (isHighResolution) ? 18 : 10; xOffset = (isHighResolution) ? 18 : 10;
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset, display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
@@ -313,7 +315,8 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// === Set Title, Blank for Clock // === Set Title, Blank for Clock
const char *titleStr = ""; const char *titleStr = "";
// === Header === // === Header ===
graphics::drawCommonHeader(display, x, y, titleStr, true); graphics::drawCommonHeader(display, x, y, titleStr, true, true);
int line = 0;
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) { if (nimbleBluetooth && nimbleBluetooth->isConnected()) {

View File

@@ -277,12 +277,13 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds); std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
// Line 1 (Still) // Line 1 (Still)
#if !defined(M5STACK_UNITC6L)
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str()); display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
if (config.display.heading_bold) if (config.display.heading_bold)
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str()); display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
display->setColor(WHITE); display->setColor(WHITE);
#endif
// Setup string to assemble analogClock string // Setup string to assemble analogClock string
std::string analogClock = ""; std::string analogClock = "";
@@ -329,8 +330,7 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
#if HAS_GPS #if HAS_GPS
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
// Line 3 // Line 3
if (config.display.gps_format != if (uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
UIRenderer::drawGpsAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus); UIRenderer::drawGpsAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
// Line 4 // Line 4
@@ -386,31 +386,56 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
char shortnameble[35]; char shortnameble[35];
getMacAddr(dmac); getMacAddr(dmac);
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]); snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
#if defined(M5STACK_UNITC6L)
snprintf(shortnameble, sizeof(shortnameble), "%s", screen->ourId);
#else
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId); snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
#endif
int textWidth = display->getStringWidth(shortnameble); int textWidth = display->getStringWidth(shortnameble);
int nameX = (SCREEN_WIDTH - textWidth); int nameX = (SCREEN_WIDTH - textWidth);
display->drawString(nameX, getTextPositions(display)[line++], shortnameble); display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
// === Second Row: Radio Preset === // === Second Row: Role ===
auto role = DisplayFormatters::getDeviceRole(config.device.role);
char device_role[25];
snprintf(device_role, sizeof(device_role), "Role: %s", role);
textWidth = display->getStringWidth(device_role);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], device_role);
// === Third Row: Radio Preset ===
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset); auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
char regionradiopreset[25]; char regionradiopreset[25];
const char *region = myRegion ? myRegion->name : NULL; const char *region = myRegion ? myRegion->name : NULL;
if (region != nullptr) { if (region != nullptr) {
#if defined(M5STACK_UNITC6L)
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region);
#else
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode); snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
#endif
} }
textWidth = display->getStringWidth(regionradiopreset); textWidth = display->getStringWidth(regionradiopreset);
nameX = (SCREEN_WIDTH - textWidth) / 2; nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], regionradiopreset); display->drawString(nameX, getTextPositions(display)[line++], regionradiopreset);
// === Third Row: Frequency / ChanNum === // === Fourth Row: Frequency / ChanNum ===
char frequencyslot[35]; char frequencyslot[35];
char freqStr[16]; char freqStr[16];
float freq = RadioLibInterface::instance->getFreq(); float freq = RadioLibInterface::instance->getFreq();
snprintf(freqStr, sizeof(freqStr), "%.3f", freq); snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
if (config.lora.channel_num == 0) { if (config.lora.channel_num == 0) {
#if defined(M5STACK_UNITC6L)
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz", freqStr);
#else
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr); snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
#endif
} else { } else {
#if defined(M5STACK_UNITC6L)
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num);
#else
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num); snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
#endif
} }
size_t len = strlen(frequencyslot); size_t len = strlen(frequencyslot);
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) { if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
@@ -420,7 +445,8 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2; nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot); display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
// === Fourth Row: Channel Utilization === #if !defined(M5STACK_UNITC6L)
// === Fifth Row: Channel Utilization ===
const char *chUtil = "ChUtil:"; const char *chUtil = "ChUtil:";
char chUtilPercentage[10]; char chUtilPercentage[10];
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent()); snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
@@ -437,7 +463,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2; int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
int starting_position = centerofscreen - total_line_content_width; int starting_position = centerofscreen - total_line_content_width;
display->drawString(starting_position, getTextPositions(display)[line++], chUtil); display->drawString(starting_position, getTextPositions(display)[line], chUtil);
// Force 56% or higher to show a full 100% bar, text would still show related percent. // Force 56% or higher to show a full 100% bar, text would still show related percent.
if (chutil_percent >= 61) { if (chutil_percent >= 61) {
@@ -474,14 +500,15 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height); display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height);
} }
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[4], display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
chUtilPercentage); chUtilPercentage);
#endif
} }
// **************************** // ****************************
// * System Screen * // * System Screen *
// **************************** // ****************************
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{ {
display->clear(); display->clear();
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
@@ -501,8 +528,11 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
#ifdef USE_EINK #ifdef USE_EINK
barsOffset -= 12; barsOffset -= 12;
#endif #endif
#if defined(M5STACK_UNITC6L)
const int barX = x + 45 + barsOffset;
#else
const int barX = x + 40 + barsOffset; const int barX = x + 40 + barsOffset;
#endif
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) { auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
if (total == 0) if (total == 0)
return; return;
@@ -527,7 +557,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
// Label // Label
display->setTextAlignment(TEXT_ALIGN_LEFT); display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(labelX, getTextPositions(display)[line], label); display->drawString(labelX, getTextPositions(display)[line], label);
#if !defined(M5STACK_UNITC6L)
// Bar // Bar
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2; int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
display->setColor(WHITE); display->setColor(WHITE);
@@ -535,7 +565,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->fillRect(barX, barY, fillWidth, barHeight); display->fillRect(barX, barY, fillWidth, barHeight);
display->setColor(WHITE); display->setColor(WHITE);
#endif
// Value string // Value string
display->setTextAlignment(TEXT_ALIGN_RIGHT); display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr); display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
@@ -588,10 +618,16 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
line += 1; line += 1;
} }
line += 1; line += 1;
char appversionstr[35]; char appversionstr[35];
snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION)); snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION));
char appversionstr_formatted[40]; char appversionstr_formatted[40];
char *lastDot = strrchr(appversionstr, '.'); char *lastDot = strrchr(appversionstr, '.');
#if defined(M5STACK_UNITC6L)
if (lastDot != nullptr) {
*lastDot = '\0'; // truncate string
}
#else
if (lastDot) { if (lastDot) {
size_t prefixLen = lastDot - appversionstr; size_t prefixLen = lastDot - appversionstr;
strncpy(appversionstr_formatted, appversionstr, prefixLen); strncpy(appversionstr_formatted, appversionstr, prefixLen);
@@ -602,10 +638,12 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1); strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
appversionstr[sizeof(appversionstr) - 1] = '\0'; appversionstr[sizeof(appversionstr) - 1] = '\0';
} }
#endif
int textWidth = display->getStringWidth(appversionstr); int textWidth = display->getStringWidth(appversionstr);
int nameX = (SCREEN_WIDTH - textWidth) / 2; int nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
#if !defined(M5STACK_UNITC6L)
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
line += 1; line += 1;
char uptimeStr[32] = ""; char uptimeStr[32] = "";
@@ -624,6 +662,34 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2; nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], uptimeStr); display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
} }
#endif
}
// ****************************
// * Chirpy Screen *
// ****************************
void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
int iconX = SCREEN_WIDTH - chirpy_width - (chirpy_width / 3);
int iconY = (SCREEN_HEIGHT - chirpy_height) / 2;
int textX_offset = 10;
if (isHighResolution) {
iconX = SCREEN_WIDTH - chirpy_width_hirez - (chirpy_width_hirez / 3);
iconY = (SCREEN_HEIGHT - chirpy_height_hirez) / 2;
textX_offset = textX_offset * 4;
display->drawXbm(iconX, iconY, chirpy_width_hirez, chirpy_height_hirez, chirpy_hirez);
} else {
display->drawXbm(iconX, iconY, chirpy_width, chirpy_height, chirpy);
}
int textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("Hello") / 2);
display->drawString(textX, getTextPositions(display)[line++], "Hello");
textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("World!") / 2);
display->drawString(textX, getTextPositions(display)[line++], "World!");
} }
} // namespace DebugRenderer } // namespace DebugRenderer
} // namespace graphics } // namespace graphics

View File

@@ -31,8 +31,11 @@ void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state
// LoRa information display // LoRa information display
void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// Memory screen display // System screen display
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// Chirpy screen display
void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
} // namespace DebugRenderer } // namespace DebugRenderer
} // namespace graphics } // namespace graphics

View File

@@ -26,6 +26,26 @@ menuHandler::screenMenus menuHandler::menuQueue = menu_none;
bool test_enabled = false; bool test_enabled = false;
uint8_t test_count = 0; uint8_t test_count = 0;
void menuHandler::loraMenu()
{
static const char *optionsArray[] = {"Back", "Region Picker", "Device Role"};
enum optionsNumbers { Back = 0, lora_picker = 1, device_role_picker = 2 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "LoRa Actions";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
// No action
} else if (selected == lora_picker) {
menuHandler::menuQueue = menuHandler::lora_picker;
} else if (selected == device_role_picker) {
menuHandler::menuQueue = menuHandler::device_role_picker;
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::OnboardMessage() void menuHandler::OnboardMessage()
{ {
static const char *optionsArray[] = {"OK", "Got it!"}; static const char *optionsArray[] = {"OK", "Got it!"};
@@ -79,7 +99,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
"NP_865", "NP_865",
"BR_902"}; "BR_902"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "LoRa Region";
#else
bannerOptions.message = "Set the LoRa region"; bannerOptions.message = "Set the LoRa region";
#endif
bannerOptions.durationMs = duration; bannerOptions.durationMs = duration;
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 27; bannerOptions.optionsCount = 27;
@@ -119,6 +143,40 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::DeviceRolePicker()
{
static const char *optionsArray[] = {"Back", "Client", "Client Mute", "Lost and Found", "Tracker"};
enum optionsNumbers {
Back = 0,
devicerole_client = 1,
devicerole_clientmute = 2,
devicerole_lostandfound = 3,
devicerole_tracker = 4
};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Device Role";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
menuHandler::menuQueue = menuHandler::lora_Menu;
screen->runNow();
return;
} else if (selected == devicerole_client) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
} else if (selected == devicerole_clientmute) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE;
} else if (selected == devicerole_lostandfound) {
config.device.role = meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND;
} else if (selected == devicerole_tracker) {
config.device.role = meshtastic_Config_DeviceConfig_Role_TRACKER;
}
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::TwelveHourPicker() void menuHandler::TwelveHourPicker()
{ {
static const char *optionsArray[] = {"Back", "12-hour", "24-hour"}; static const char *optionsArray[] = {"Back", "12-hour", "24-hour"};
@@ -260,7 +318,11 @@ void menuHandler::TZPicker()
void menuHandler::clockMenu() void menuHandler::clockMenu()
{ {
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[] = {"Back", "Time Format", "Timezone"};
#else
static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"}; static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"};
#endif
enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 }; enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 };
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Clock Action"; bannerOptions.message = "Clock Action";
@@ -284,8 +346,11 @@ void menuHandler::clockMenu()
void menuHandler::messageResponseMenu() void menuHandler::messageResponseMenu()
{ {
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 }; enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply Preset"};
#else
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"}; static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
#endif
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset}; static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
int options = 3; int options = 3;
@@ -299,13 +364,17 @@ void menuHandler::messageResponseMenu()
optionsEnumArray[options++] = Aloud; optionsEnumArray[options++] = Aloud;
#endif #endif
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Message";
#else
bannerOptions.message = "Message Action"; bannerOptions.message = "Message Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dismiss) { if (selected == Dismiss) {
screen->dismissCurrentFrame(); screen->hideCurrentFrame();
} else if (selected == Preset) { } else if (selected == Preset) {
if (devicestate.rx_text_message.to == NODENUM_BROADCAST) { if (devicestate.rx_text_message.to == NODENUM_BROADCAST) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel); cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel);
@@ -346,10 +415,17 @@ void menuHandler::homeBaseMenu()
optionsArray[options] = "Sleep Screen"; optionsArray[options] = "Sleep Screen";
optionsEnumArray[options++] = Sleep; optionsEnumArray[options++] = Sleep;
#endif #endif
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
optionsArray[options] = "Send Position"; optionsArray[options] = "Send Position";
} else {
optionsArray[options] = "Send Node Info";
}
optionsEnumArray[options++] = Position; optionsEnumArray[options++] = Position;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "New Preset";
#else
optionsArray[options] = "New Preset Msg"; optionsArray[options] = "New Preset Msg";
#endif
optionsEnumArray[options++] = Preset; optionsEnumArray[options++] = Preset;
if (kb_found) { if (kb_found) {
optionsArray[options] = "New Freetext Msg"; optionsArray[options] = "New Freetext Msg";
@@ -357,7 +433,11 @@ void menuHandler::homeBaseMenu()
} }
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Home";
#else
bannerOptions.message = "Home Action"; bannerOptions.message = "Home Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
@@ -396,6 +476,11 @@ void menuHandler::homeBaseMenu()
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::textMessageMenu()
{
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
}
void menuHandler::textMessageBaseMenu() void menuHandler::textMessageBaseMenu()
{ {
enum optionsNumbers { Back, Preset, Freetext, enumEnd }; enum optionsNumbers { Back, Preset, Freetext, enumEnd };
@@ -427,7 +512,7 @@ void menuHandler::textMessageBaseMenu()
void menuHandler::systemBaseMenu() void menuHandler::systemBaseMenu()
{ {
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd }; enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"}; static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back}; static int optionsEnumArray[enumEnd] = {Back};
int options = 1; int options = 1;
@@ -440,10 +525,19 @@ void menuHandler::systemBaseMenu()
optionsEnumArray[options++] = ScreenOptions; optionsEnumArray[options++] = ScreenOptions;
#endif #endif
optionsArray[options] = "Frame Visiblity Toggle";
optionsEnumArray[options++] = FrameToggles;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Bluetooth";
#else
optionsArray[options] = "Bluetooth Toggle"; optionsArray[options] = "Bluetooth Toggle";
#endif
optionsEnumArray[options++] = Bluetooth; optionsEnumArray[options++] = Bluetooth;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Power";
#else
optionsArray[options] = "Reboot/Shutdown"; optionsArray[options] = "Reboot/Shutdown";
#endif
optionsEnumArray[options++] = PowerMenu; optionsEnumArray[options++] = PowerMenu;
if (test_enabled) { if (test_enabled) {
@@ -452,7 +546,11 @@ void menuHandler::systemBaseMenu()
} }
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "System";
#else
bannerOptions.message = "System Action"; bannerOptions.message = "System Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -466,6 +564,9 @@ void menuHandler::systemBaseMenu()
} else if (selected == PowerMenu) { } else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::power_menu; menuHandler::menuQueue = menuHandler::power_menu;
screen->runNow(); screen->runNow();
} else if (selected == FrameToggles) {
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == Test) { } else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu; menuHandler::menuQueue = menuHandler::test_menu;
screen->runNow(); screen->runNow();
@@ -485,7 +586,11 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu() void menuHandler::favoriteBaseMenu()
{ {
enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd }; enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd };
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[enumEnd] = {"Back", "New Preset"};
#else
static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"}; static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
#endif
static int optionsEnumArray[enumEnd] = {Back, Preset}; static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2; int options = 2;
@@ -493,13 +598,19 @@ void menuHandler::favoriteBaseMenu()
optionsArray[options] = "New Freetext Msg"; optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext; optionsEnumArray[options++] = Freetext;
} }
#if !defined(M5STACK_UNITC6L)
optionsArray[options] = "Trace Route"; optionsArray[options] = "Trace Route";
optionsEnumArray[options++] = TraceRoute; optionsEnumArray[options++] = TraceRoute;
#endif
optionsArray[options] = "Remove Favorite"; optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove; optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Favorites";
#else
bannerOptions.message = "Favorites Action"; bannerOptions.message = "Favorites Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
@@ -522,16 +633,17 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu() void menuHandler::positionBaseMenu()
{ {
enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd }; enum optionsNumbers { Back, GPSToggle, GPSFormat, CompassMenu, CompassCalibrate, enumEnd };
static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"}; static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "GPS Format", "Compass"};
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu}; static int optionsEnumArray[enumEnd] = {Back, GPSToggle, GPSFormat, CompassMenu};
int options = 3; int options = 4;
if (accelerometerThread) { if (accelerometerThread) {
optionsArray[options] = "Compass Calibrate"; optionsArray[options] = "Compass Calibrate";
optionsEnumArray[options++] = CompassCalibrate; optionsEnumArray[options++] = CompassCalibrate;
} }
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action"; bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
@@ -541,6 +653,9 @@ void menuHandler::positionBaseMenu()
if (selected == GPSToggle) { if (selected == GPSToggle) {
menuQueue = gps_toggle_menu; menuQueue = gps_toggle_menu;
screen->runNow(); screen->runNow();
} else if (selected == GPSFormat) {
menuQueue = gps_format_menu;
screen->runNow();
} else if (selected == CompassMenu) { } else if (selected == CompassMenu) {
menuQueue = compass_point_north_menu; menuQueue = compass_point_north_menu;
screen->runNow(); screen->runNow();
@@ -554,11 +669,19 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu() void menuHandler::nodeListMenu()
{ {
enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd }; enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd };
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset Node"};
#else
static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"}; static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"};
#endif
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action"; bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
#if defined(M5STACK_UNITC6L)
bannerOptions.optionsCount = 3;
#else
bannerOptions.optionsCount = 5; bannerOptions.optionsCount = 5;
#endif
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Favorite) { if (selected == Favorite) {
menuQueue = add_favorite; menuQueue = add_favorite;
@@ -659,13 +782,62 @@ void menuHandler::GPSToggleMenu()
bannerOptions.InitialSelected = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED ? 1 : 2; bannerOptions.InitialSelected = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED ? 1 : 2;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::GPSFormatMenu()
{
static const char *optionsArray[] = {"Back",
isHighResolution ? "Decimal Degrees" : "DEC",
isHighResolution ? "Degrees Minutes Seconds" : "DMS",
isHighResolution ? "Universal Transverse Mercator" : "UTM",
isHighResolution ? "Military Grid Reference System" : "MGRS",
isHighResolution ? "Open Location Code" : "OLC",
isHighResolution ? "Ordnance Survey Grid Ref" : "OSGR",
isHighResolution ? "Maidenhead Locator" : "MLS"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "GPS Format";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 8;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 2) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 3) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 4) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 5) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 6) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 7) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS;
service->reloadConfig(SEGMENT_CONFIG);
} else {
menuQueue = position_base_menu;
screen->runNow();
}
};
bannerOptions.InitialSelected = uiconfig.gps_format + 1;
screen->showOverlayBanner(bannerOptions);
}
#endif #endif
void menuHandler::BluetoothToggleMenu() void menuHandler::BluetoothToggleMenu()
{ {
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Bluetooth";
#else
bannerOptions.message = "Toggle Bluetooth"; bannerOptions.message = "Toggle Bluetooth";
#endif
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3; bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
@@ -857,7 +1029,11 @@ void menuHandler::rebootMenu()
{ {
static const char *optionsArray[] = {"Back", "Confirm"}; static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Reboot";
#else
bannerOptions.message = "Reboot Device?"; bannerOptions.message = "Reboot Device?";
#endif
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2; bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
@@ -877,7 +1053,11 @@ void menuHandler::shutdownMenu()
{ {
static const char *optionsArray[] = {"Back", "Confirm"}; static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Shutdown";
#else
bannerOptions.message = "Shutdown Device?"; bannerOptions.message = "Shutdown Device?";
#endif
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2; bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
@@ -894,7 +1074,12 @@ void menuHandler::shutdownMenu()
void menuHandler::addFavoriteMenu() void menuHandler::addFavoriteMenu()
{ {
#if defined(M5STACK_UNITC6L)
screen->showNodePicker("Node Favorite", 30000, [](uint32_t nodenum) -> void {
#else
screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void { screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
#endif
LOG_WARN("Nodenum: %u", nodenum); LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum); nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
@@ -937,16 +1122,33 @@ void menuHandler::traceRouteMenu()
void menuHandler::testMenu() void menuHandler::testMenu()
{ {
static const char *optionsArray[] = {"Back", "Number Picker"}; enum optionsNumbers { Back, NumberPicker, ShowChirpy };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
optionsArray[options] = "Number Picker";
optionsEnumArray[options++] = NumberPicker;
optionsArray[options] = screen->isFrameHidden("chirpy") ? "Show Chirpy" : "Hide Chirpy";
optionsEnumArray[options++] = ShowChirpy;
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
std::string message = "Test to Run?\n"; bannerOptions.message = "Hidden Test Menu";
bannerOptions.message = message.c_str();
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2; bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == NumberPicker) {
menuQueue = number_test; menuQueue = number_test;
screen->runNow(); screen->runNow();
} else if (selected == ShowChirpy) {
screen->toggleFrameVisibility("chirpy");
screen->setFrames(Screen::FOCUS_SYSTEM);
} else {
menuQueue = system_base_menu;
screen->runNow();
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@@ -1090,7 +1292,11 @@ void menuHandler::powerMenu()
#endif #endif
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Power";
#else
bannerOptions.message = "Reboot / Shutdown"; bannerOptions.message = "Reboot / Shutdown";
#endif
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -1143,6 +1349,116 @@ void menuHandler::keyVerificationFinalPrompt()
} }
} }
void menuHandler::FrameToggles_menu()
{
enum optionsNumbers {
Finish,
nodelist,
nodelist_lastheard,
nodelist_hopsignal,
nodelist_distance,
nodelist_bearings,
gps,
lora,
clock,
show_favorites,
enumEnd
};
static const char *optionsArray[enumEnd] = {"Finish"};
static int optionsEnumArray[enumEnd] = {Finish};
int options = 1;
// Track last selected index (not enum value!)
static int lastSelectedIndex = 0;
#ifndef USE_EINK
optionsArray[options] = screen->isFrameHidden("nodelist") ? "Show Node List" : "Hide Node List";
optionsEnumArray[options++] = nodelist;
#endif
#ifdef USE_EINK
optionsArray[options] = screen->isFrameHidden("nodelist_lastheard") ? "Show NL - Last Heard" : "Hide NL - Last Heard";
optionsEnumArray[options++] = nodelist_lastheard;
optionsArray[options] = screen->isFrameHidden("nodelist_hopsignal") ? "Show NL - Hops/Signal" : "Hide NL - Hops/Signal";
optionsEnumArray[options++] = nodelist_hopsignal;
optionsArray[options] = screen->isFrameHidden("nodelist_distance") ? "Show NL - Distance" : "Hide NL - Distance";
optionsEnumArray[options++] = nodelist_distance;
#endif
#if HAS_GPS
optionsArray[options] = screen->isFrameHidden("nodelist_bearings") ? "Show Bearings" : "Hide Bearings";
optionsEnumArray[options++] = nodelist_bearings;
optionsArray[options] = screen->isFrameHidden("gps") ? "Show Position" : "Hide Position";
optionsEnumArray[options++] = gps;
#endif
optionsArray[options] = screen->isFrameHidden("lora") ? "Show LoRa" : "Hide LoRa";
optionsEnumArray[options++] = lora;
optionsArray[options] = screen->isFrameHidden("clock") ? "Show Clock" : "Hide Clock";
optionsEnumArray[options++] = clock;
optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
optionsEnumArray[options++] = show_favorites;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Show/Hide Frames";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.InitialSelected = lastSelectedIndex; // Use index, not enum value
bannerOptions.bannerCallback = [options](int selected) mutable -> void {
// Find the index of selected in optionsEnumArray
int idx = 0;
for (; idx < options; ++idx) {
if (optionsEnumArray[idx] == selected)
break;
}
lastSelectedIndex = idx;
if (selected == Finish) {
screen->setFrames(Screen::FOCUS_DEFAULT);
} else if (selected == nodelist) {
screen->toggleFrameVisibility("nodelist");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_lastheard) {
screen->toggleFrameVisibility("nodelist_lastheard");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_hopsignal) {
screen->toggleFrameVisibility("nodelist_hopsignal");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_distance) {
screen->toggleFrameVisibility("nodelist_distance");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_bearings) {
screen->toggleFrameVisibility("nodelist_bearings");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == gps) {
screen->toggleFrameVisibility("gps");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == lora) {
screen->toggleFrameVisibility("lora");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == clock) {
screen->toggleFrameVisibility("clock");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == show_favorites) {
screen->toggleFrameVisibility("show_favorites");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::handleMenuSwitch(OLEDDisplay *display) void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{ {
if (menuQueue != menu_none) if (menuQueue != menu_none)
@@ -1150,9 +1466,15 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
switch (menuQueue) { switch (menuQueue) {
case menu_none: case menu_none:
break; break;
case lora_Menu:
loraMenu();
break;
case lora_picker: case lora_picker:
LoraRegionPicker(); LoraRegionPicker();
break; break;
case device_role_picker:
DeviceRolePicker();
break;
case no_timeout_lora_picker: case no_timeout_lora_picker:
LoraRegionPicker(0); LoraRegionPicker(0);
break; break;
@@ -1178,6 +1500,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case gps_toggle_menu: case gps_toggle_menu:
GPSToggleMenu(); GPSToggleMenu();
break; break;
case gps_format_menu:
GPSFormatMenu();
break;
#endif #endif
case compass_point_north_menu: case compass_point_north_menu:
compassNorthMenu(); compassNorthMenu();
@@ -1239,6 +1564,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case power_menu: case power_menu:
powerMenu(); powerMenu();
break; break;
case FrameToggles:
FrameToggles_menu();
break;
case throttle_message: case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000); screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break; break;

View File

@@ -9,7 +9,9 @@ class menuHandler
public: public:
enum screenMenus { enum screenMenus {
menu_none, menu_none,
lora_Menu,
lora_picker, lora_picker,
device_role_picker,
no_timeout_lora_picker, no_timeout_lora_picker,
TZ_picker, TZ_picker,
twelve_hour_picker, twelve_hour_picker,
@@ -17,6 +19,7 @@ class menuHandler
clock_menu, clock_menu,
position_base_menu, position_base_menu,
gps_toggle_menu, gps_toggle_menu,
gps_format_menu,
compass_point_north_menu, compass_point_north_menu,
reset_node_db_menu, reset_node_db_menu,
buzzermodemenupicker, buzzermodemenupicker,
@@ -39,11 +42,14 @@ class menuHandler
key_verification_final_prompt, key_verification_final_prompt,
trace_route_menu, trace_route_menu,
throttle_message, throttle_message,
FrameToggles
}; };
static screenMenus menuQueue; static screenMenus menuQueue;
static void OnboardMessage(); static void OnboardMessage();
static void LoraRegionPicker(uint32_t duration = 30000); static void LoraRegionPicker(uint32_t duration = 30000);
static void loraMenu();
static void DeviceRolePicker();
static void handleMenuSwitch(OLEDDisplay *display); static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm); static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
static void clockMenu(); static void clockMenu();
@@ -58,6 +64,7 @@ class menuHandler
static void positionBaseMenu(); static void positionBaseMenu();
static void compassNorthMenu(); static void compassNorthMenu();
static void GPSToggleMenu(); static void GPSToggleMenu();
static void GPSFormatMenu();
static void BuzzerModeMenu(); static void BuzzerModeMenu();
static void switchToMUIMenu(); static void switchToMUIMenu();
static void TFTColorPickerMenu(OLEDDisplay *display); static void TFTColorPickerMenu(OLEDDisplay *display);
@@ -76,6 +83,8 @@ class menuHandler
static void notificationsMenu(); static void notificationsMenu();
static void screenOptionsMenu(); static void screenOptionsMenu();
static void powerMenu(); static void powerMenu();
static void textMessageMenu();
static void FrameToggles_menu();
private: private:
static void saveUIConfig(); static void saveUIConfig();

View File

@@ -181,12 +181,19 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
display->clear(); display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT); display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
#if defined(M5STACK_UNITC6L)
const int fixedTopHeight = 24;
const int windowX = 0;
const int windowY = fixedTopHeight;
const int windowWidth = 64;
const int windowHeight = SCREEN_HEIGHT - fixedTopHeight;
#else
const int navHeight = FONT_HEIGHT_SMALL; const int navHeight = FONT_HEIGHT_SMALL;
const int scrollBottom = SCREEN_HEIGHT - navHeight; const int scrollBottom = SCREEN_HEIGHT - navHeight;
const int usableHeight = scrollBottom; const int usableHeight = scrollBottom;
const int textWidth = SCREEN_WIDTH; const int textWidth = SCREEN_WIDTH;
#endif
bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED); bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
bool isBold = config.display.heading_bold; bool isBold = config.display.heading_bold;
@@ -201,7 +208,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
graphics::drawCommonHeader(display, x, y, titleStr); graphics::drawCommonHeader(display, x, y, titleStr);
const char *messageString = "No messages"; const char *messageString = "No messages";
int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2); int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2);
#if defined(M5STACK_UNITC6L)
display->drawString(center_text, windowY + (windowHeight / 2) - (FONT_HEIGHT_SMALL / 2) - 5, messageString);
#else
display->drawString(center_text, getTextPositions(display)[2], messageString); display->drawString(center_text, getTextPositions(display)[2], messageString);
#endif
return; return;
} }
@@ -209,6 +220,10 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
char headerStr[80]; char headerStr[80];
const char *sender = "???"; const char *sender = "???";
#if defined(M5STACK_UNITC6L)
if (node && node->has_user)
sender = node->user.short_name;
#else
if (node && node->has_user) { if (node && node->has_user) {
if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) { if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) {
sender = node->user.long_name; sender = node->user.long_name;
@@ -216,6 +231,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
sender = node->user.short_name; sender = node->user.short_name;
} }
} }
#endif
uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24; uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
uint8_t timestampHours, timestampMinutes; uint8_t timestampHours, timestampMinutes;
int32_t daysAgo; int32_t daysAgo;
@@ -235,10 +251,61 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
sender); sender);
} }
} else { } else {
#if defined(M5STACK_UNITC6L)
snprintf(headerStr, sizeof(headerStr), "%s from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
sender);
#else
snprintf(headerStr, sizeof(headerStr), "%s ago from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(), snprintf(headerStr, sizeof(headerStr), "%s ago from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
sender); sender);
#endif
} }
#if defined(M5STACK_UNITC6L)
graphics::drawCommonHeader(display, x, y, titleStr);
int headerY = getTextPositions(display)[1];
display->drawString(x, headerY, headerStr);
for (int separatorX = 0; separatorX < SCREEN_WIDTH; separatorX += 2) {
display->setPixel(separatorX, fixedTopHeight - 1);
}
cachedLines.clear();
std::string fullMsg(messageBuf);
std::string currentLine;
for (size_t i = 0; i < fullMsg.size();) {
unsigned char c = fullMsg[i];
size_t charLen = 1;
if ((c & 0xE0) == 0xC0)
charLen = 2;
else if ((c & 0xF0) == 0xE0)
charLen = 3;
else if ((c & 0xF8) == 0xF0)
charLen = 4;
std::string nextChar = fullMsg.substr(i, charLen);
std::string testLine = currentLine + nextChar;
if (display->getStringWidth(testLine.c_str()) > windowWidth) {
cachedLines.push_back(currentLine);
currentLine = nextChar;
} else {
currentLine = testLine;
}
i += charLen;
}
if (!currentLine.empty())
cachedLines.push_back(currentLine);
cachedHeights = calculateLineHeights(cachedLines, emotes);
int yOffset = windowY;
int linesDrawn = 0;
for (size_t i = 0; i < cachedLines.size(); ++i) {
if (linesDrawn >= 2)
break;
int lineHeight = cachedHeights[i];
if (yOffset + lineHeight > windowY + windowHeight)
break;
drawStringWithEmotes(display, windowX, yOffset, cachedLines[i], emotes, numEmotes);
yOffset += lineHeight;
linesDrawn++;
}
screen->forceDisplay();
#else
uint32_t now = millis(); uint32_t now = millis();
#ifndef EXCLUDE_EMOJI #ifndef EXCLUDE_EMOJI
// === Bounce animation setup === // === Bounce animation setup ===
@@ -355,6 +422,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Draw header at the end to sort out overlapping elements // Draw header at the end to sort out overlapping elements
graphics::drawCommonHeader(display, x, y, titleStr); graphics::drawCommonHeader(display, x, y, titleStr);
#endif
} }
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth) std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)

View File

@@ -21,6 +21,10 @@ extern bool haveGlyphs(const char *str);
// Global screen instance // Global screen instance
extern graphics::Screen *screen; extern graphics::Screen *screen;
#if defined(M5STACK_UNITC6L)
static uint32_t lastSwitchTime = 0;
#else
#endif
namespace graphics namespace graphics
{ {
namespace NodeListRenderer namespace NodeListRenderer
@@ -393,9 +397,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
{ {
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1; const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
const int rowYOffset = FONT_HEIGHT_SMALL - 3; const int rowYOffset = FONT_HEIGHT_SMALL - 3;
#if defined(M5STACK_UNITC6L)
int columnWidth = display->getWidth();
#else
int columnWidth = display->getWidth() / 2; int columnWidth = display->getWidth() / 2;
#endif
display->clear(); display->clear();
// Draw the battery/time header // Draw the battery/time header
@@ -408,8 +414,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset; int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
int visibleNodeRows = totalRowsAvailable; int visibleNodeRows = totalRowsAvailable;
#if defined(M5STACK_UNITC6L)
int totalColumns = 1;
#else
int totalColumns = 2; int totalColumns = 2;
#endif
int startIndex = scrollIndex * visibleNodeRows * totalColumns; int startIndex = scrollIndex * visibleNodeRows * totalColumns;
if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) { if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) {
startIndex++; // skip own node startIndex++; // skip own node
@@ -445,12 +454,14 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
} }
} }
#if !defined(M5STACK_UNITC6L)
// Draw column separator // Draw column separator
if (shownCount > 0) { if (shownCount > 0) {
const int firstNodeY = y + 3; const int firstNodeY = y + 3;
drawColumnSeparator(display, x, firstNodeY, lastNodeY); drawColumnSeparator(display, x, firstNodeY, lastNodeY);
} }
#endif
const int scrollStartY = y + 3; const int scrollStartY = y + 3;
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY); drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
} }
@@ -468,6 +479,13 @@ void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state,
unsigned long now = millis(); unsigned long now = millis();
#if defined(M5STACK_UNITC6L)
display->clear();
if (now - lastSwitchTime >= 3000) {
display->display();
lastSwitchTime = now;
}
#endif
// On very first call (on boot or state enter) // On very first call (on boot or state enter)
if (lastRenderedMode == MODE_COUNT) { if (lastRenderedMode == MODE_COUNT) {
currentMode = MODE_LAST_HEARD; currentMode = MODE_LAST_HEARD;
@@ -522,6 +540,14 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
double lat = DegD(ourNode->position.latitude_i); double lat = DegD(ourNode->position.latitude_i);
double lon = DegD(ourNode->position.longitude_i); double lon = DegD(ourNode->position.longitude_i);
#if defined(M5STACK_UNITC6L)
display->clear();
uint32_t now = millis();
if (now - lastSwitchTime >= 2000) {
display->display();
lastSwitchTime = now;
}
#endif
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
#if HAS_GPS #if HAS_GPS
if (screen->hasHeading()) { if (screen->hasHeading()) {

View File

@@ -216,9 +216,11 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
} }
// Handle input // Handle input
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--; curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++; curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
alertBannerCallback(selectedNodenum); alertBannerCallback(selectedNodenum);
@@ -333,9 +335,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
// Handle input // Handle input
if (alertBannerOptions > 0) { if (alertBannerOptions > 0) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--; curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++; curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
if (optionsEnumPtr != nullptr) { if (optionsEnumPtr != nullptr) {
@@ -459,6 +463,135 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
// count lines // count lines
uint16_t boxWidth = hPadding * 2 + maxWidth; uint16_t boxWidth = hPadding * 2 + maxWidth;
#if defined(M5STACK_UNITC6L)
if (needs_bell) {
if (isHighResolution && boxWidth <= 150)
boxWidth += 26;
if (!isHighResolution && boxWidth <= 100)
boxWidth += 20;
}
uint16_t screenHeight = display->height();
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
uint16_t boxHeight = contentHeight + vPadding * 2;
if (visibleTotalLines == 1)
boxHeight += (isHighResolution ? 4 : 3);
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
if (totalLines > visibleTotalLines)
boxWidth += (isHighResolution ? 4 : 2);
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
if (visibleTotalLines == 1) {
boxTop += 25;
}
if (alertBannerOptions < 3) {
int missingLines = 3 - alertBannerOptions;
int moveUp = missingLines * (effectiveLineHeight / 2);
boxTop -= moveUp;
if (boxTop < 0)
boxTop = 0;
}
// === Draw Box ===
display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, boxWidth, boxHeight);
display->setColor(WHITE);
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
display->fillRect(boxLeft - 2, boxTop, 1, boxHeight);
display->fillRect(boxLeft + boxWidth + 1, boxTop, 1, boxHeight);
display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1);
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
display->setColor(WHITE);
int16_t lineY = boxTop + vPadding;
int swingRange = 8;
static int swingOffset = 0;
static bool swingRight = true;
static unsigned long lastSwingTime = 0;
unsigned long now = millis();
int swingSpeedMs = 10 / (swingRange * 2);
if (now - lastSwingTime >= (unsigned long)swingSpeedMs) {
lastSwingTime = now;
if (swingRight) {
swingOffset++;
if (swingOffset >= swingRange)
swingRight = false;
} else {
swingOffset--;
if (swingOffset <= 0)
swingRight = true;
}
}
for (int i = 0; i < lineCount; i++) {
bool isTitle = (i == 0);
int globalOptionIndex = (i - 1) + firstOptionToShow;
bool isSelectedOption = (!isTitle && globalOptionIndex >= 0 && globalOptionIndex == curSelected);
uint16_t visibleWidth = 64 - hPadding * 2;
if (totalLines > visibleTotalLines)
visibleWidth -= 6;
char lineBuffer[lineLengths[i] + 1];
strncpy(lineBuffer, lines[i], lineLengths[i]);
lineBuffer[lineLengths[i]] = '\0';
if (isTitle) {
if (visibleTotalLines == 1) {
display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
display->setColor(WHITE);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
} else {
display->setColor(WHITE);
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
display->setColor(BLACK);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
display->setColor(WHITE);
if (needs_bell) {
int bellY = boxTop + (FONT_HEIGHT_SMALL - 8) / 2;
display->drawXbm(boxLeft + (boxWidth - lineWidths[i]) / 2 - 10, bellY, 8, 8, bell_alert);
display->drawXbm(boxLeft + (boxWidth + lineWidths[i]) / 2 + 2, bellY, 8, 8, bell_alert);
}
}
lineY = boxTop + effectiveLineHeight + 1;
} else if (isSelectedOption) {
display->setColor(WHITE);
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
display->setColor(BLACK);
if (lineLengths[i] > 15 && lineWidths[i] > visibleWidth) {
int textX = boxLeft + hPadding + swingOffset;
display->drawString(textX, lineY - 1, lineBuffer);
} else {
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY - 1, lineBuffer);
}
display->setColor(WHITE);
lineY += effectiveLineHeight;
} else {
display->setColor(BLACK);
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
display->setColor(WHITE);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY, lineBuffer);
lineY += effectiveLineHeight;
}
}
if (totalLines > visibleTotalLines) {
const uint8_t scrollBarWidth = 5;
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
float ratio = (float)visibleTotalLines / totalLines;
uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4);
float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines);
uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight);
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
}
#else
if (needs_bell) { if (needs_bell) {
if (isHighResolution && boxWidth <= 150) if (isHighResolution && boxWidth <= 150)
boxWidth += 26; boxWidth += 26;
@@ -547,6 +680,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight); display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight); display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
} }
#endif
} }
/// Draw the last text message we received /// Draw the last text message we received

View File

@@ -20,7 +20,7 @@
// External variables // External variables
extern graphics::Screen *screen; extern graphics::Screen *screen;
static uint32_t lastSwitchTime = 0;
namespace graphics namespace graphics
{ {
NodeNum UIRenderer::currentFavoriteNodeNum = 0; NodeNum UIRenderer::currentFavoriteNodeNum = 0;
@@ -116,64 +116,120 @@ void UIRenderer::drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, con
} }
// Draw GPS status coordinates // Draw GPS status coordinates
void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps) void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps,
const char *mode)
{ {
auto gpsFormat = config.display.gps_format; auto gpsFormat = uiconfig.gps_format;
char displayLine[32]; char displayLine[32];
if (!gps->getIsConnected() && !config.position.fixed_position) { if (!gps->getIsConnected() && !config.position.fixed_position) {
strcpy(displayLine, "No GPS present"); strcpy(displayLine, "No GPS present");
display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine); display->drawString(x, y, displayLine);
} else if (!gps->getHasLock() && !config.position.fixed_position) { } else if (!gps->getHasLock() && !config.position.fixed_position) {
strcpy(displayLine, "No GPS Lock"); strcpy(displayLine, "No GPS Lock");
display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine); display->drawString(x, y, displayLine);
} else { } else {
geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) { if (gpsFormat != meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS) {
char coordinateLine[22]; char coordinateLine_1[22];
if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees char coordinateLine_2[22];
snprintf(coordinateLine, sizeof(coordinateLine), "%f %f", geoCoord.getLatitude() * 1e-7, if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees
geoCoord.getLongitude() * 1e-7); snprintf(coordinateLine_1, sizeof(coordinateLine_1), "Lat: %f", geoCoord.getLatitude() * 1e-7);
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %f", geoCoord.getLongitude() * 1e-7);
snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %06u %07u", geoCoord.getUTMZone(), geoCoord.getUTMBand(), } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator
geoCoord.getUTMEasting(), geoCoord.getUTMNorthing()); snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %06u E", geoCoord.getUTMZone(),
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System geoCoord.getUTMBand(), geoCoord.getUTMEasting());
snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %1c%1c %05u %05u", geoCoord.getMGRSZone(), snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%07u N", geoCoord.getUTMNorthing());
geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k(), } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System
geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing()); snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %1c%1c", geoCoord.getMGRSZone(),
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) { // Open Location Code geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k());
geoCoord.getOLCCode(coordinateLine); snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getMGRSEasting(),
} else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference geoCoord.getMGRSNorthing());
if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC) { // Open Location Code
snprintf(coordinateLine, sizeof(coordinateLine), "%s", "Out of Boundary"); geoCoord.getOLCCode(coordinateLine_1);
else coordinateLine_2[0] = '\0';
snprintf(coordinateLine, sizeof(coordinateLine), "%1c%1c %05u %05u", geoCoord.getOSGRE100k(), } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference
geoCoord.getOSGRN100k(), geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing()); if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') { // OSGR is only valid around the UK region
snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%s", "Out of Boundary");
coordinateLine_2[0] = '\0';
} else {
snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%1c%1c", geoCoord.getOSGRE100k(),
geoCoord.getOSGRN100k());
snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getOSGREasting(),
geoCoord.getOSGRNorthing());
}
} else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS) { // Maidenhead Locator System
double lat = geoCoord.getLatitude() * 1e-7;
double lon = geoCoord.getLongitude() * 1e-7;
// Normalize
if (lat > 90.0)
lat = 90.0;
if (lat < -90.0)
lat = -90.0;
while (lon < -180.0)
lon += 360.0;
while (lon >= 180.0)
lon -= 360.0;
double adjLon = lon + 180.0;
double adjLat = lat + 90.0;
char maiden[10]; // enough for 8-char + null
// Field (2 letters)
int lonField = int(adjLon / 20.0);
int latField = int(adjLat / 10.0);
adjLon -= lonField * 20.0;
adjLat -= latField * 10.0;
// Square (2 digits)
int lonSquare = int(adjLon / 2.0);
int latSquare = int(adjLat / 1.0);
adjLon -= lonSquare * 2.0;
adjLat -= latSquare * 1.0;
// Subsquare (2 letters)
double lonUnit = 2.0 / 24.0;
double latUnit = 1.0 / 24.0;
int lonSub = int(adjLon / lonUnit);
int latSub = int(adjLat / latUnit);
snprintf(maiden, sizeof(maiden), "%c%c%c%c%c%c", 'A' + lonField, 'A' + latField, '0' + lonSquare, '0' + latSquare,
'A' + lonSub, 'A' + latSub);
snprintf(coordinateLine_1, sizeof(coordinateLine_1), "MH: %s", maiden);
coordinateLine_2[0] = '\0'; // only need one line
} }
// If fixed position, display text "Fixed GPS" alternating with the coordinates. if (strcmp(mode, "line1") == 0) {
if (config.position.fixed_position) { display->drawString(x, y, coordinateLine_1);
if ((millis() / 10000) % 2) { } else if (strcmp(mode, "line2") == 0) {
display->drawString(x + (display->getWidth() - (display->getStringWidth(coordinateLine))) / 2, y, display->drawString(x, y, coordinateLine_2);
coordinateLine); } else if (strcmp(mode, "combined") == 0) {
} else { display->drawString(x, y, coordinateLine_1);
display->drawString(x + (display->getWidth() - (display->getStringWidth("Fixed GPS"))) / 2, y, "Fixed GPS"); if (coordinateLine_2[0] != '\0') {
display->drawString(x + display->getStringWidth(coordinateLine_1), y, coordinateLine_2);
} }
} else {
display->drawString(x + (display->getWidth() - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
} }
} else { } else {
char latLine[22]; char coordinateLine_1[22];
char lonLine[22]; char coordinateLine_2[22];
snprintf(latLine, sizeof(latLine), "%2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(), snprintf(coordinateLine_1, sizeof(coordinateLine_1), "Lat: %2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(),
geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP()); geoCoord.getDMSLatMin(), geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP());
snprintf(lonLine, sizeof(lonLine), "%3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(), snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(),
geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP()); geoCoord.getDMSLonMin(), geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP());
display->drawString(x + (display->getWidth() - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1, if (strcmp(mode, "line1") == 0) {
latLine); display->drawString(x, y, coordinateLine_1);
display->drawString(x + (display->getWidth() - (display->getStringWidth(lonLine))) / 2, y, lonLine); } else if (strcmp(mode, "line2") == 0) {
display->drawString(x, y, coordinateLine_2);
} else { // both
display->drawString(x, y, coordinateLine_1);
display->drawString(x, y + 10, coordinateLine_2);
}
} }
} }
} }
@@ -218,7 +274,6 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
// ********************** // **********************
void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y) void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y)
{ {
if (favoritedNodes.empty()) if (favoritedNodes.empty())
return; return;
@@ -230,8 +285,15 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex]; meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite) if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
return; return;
uint32_t now = millis();
display->clear(); display->clear();
#if defined(M5STACK_UNITC6L)
if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
{
display->display();
lastSwitchTime = now;
}
#endif
currentFavoriteNodeNum = node->num; currentFavoriteNodeNum = node->num;
// === Create the shortName and title string === // === Create the shortName and title string ===
const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node"; const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node";
@@ -250,9 +312,13 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
// List of available macro Y positions in order, from top to bottom. // List of available macro Y positions in order, from top to bottom.
int line = 1; // which slot to use next int line = 1; // which slot to use next
std::string usernameStr; std::string usernameStr;
// === 1. Long Name (always try to show first) === // === 1. Long Name (always try to show first) ===
#if defined(M5STACK_UNITC6L)
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr;
#else
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr; const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
#endif
if (username) { if (username) {
usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case
// Print node's long name (e.g. "Backpack Node") // Print node's long name (e.g. "Backpack Node")
@@ -307,7 +373,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
if (seenStr[0] && line < 5) { if (seenStr[0] && line < 5) {
display->drawString(x, getTextPositions(display)[line++], seenStr); display->drawString(x, getTextPositions(display)[line++], seenStr);
} }
#if !defined(M5STACK_UNITC6L)
// === 4. Uptime (only show if metric is present) === // === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = ""; char uptimeStr[32] = "";
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) { if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
@@ -479,6 +545,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
} }
// else show nothing // else show nothing
} }
#endif
} }
// **************************** // ****************************
@@ -492,7 +559,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
int line = 1; int line = 1;
// === Header === // === Header ===
#if defined(M5STACK_UNITC6L)
graphics::drawCommonHeader(display, x, y, "Home");
#else
graphics::drawCommonHeader(display, x, y, ""); graphics::drawCommonHeader(display, x, y, "");
#endif
// === Content below header === // === Content below header ===
@@ -507,20 +578,25 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
config.display.heading_bold = false; config.display.heading_bold = false;
// Display Region and Channel Utilization // Display Region and Channel Utilization
#if defined(M5STACK_UNITC6L)
drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
#else
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online"); drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
#endif
char uptimeStr[32] = ""; char uptimeStr[32] = "";
uint32_t uptime = millis() / 1000; uint32_t uptime = millis() / 1000;
uint32_t days = uptime / 86400; uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600; uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60; uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m" // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
#if !defined(M5STACK_UNITC6L)
if (days) if (days)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours); snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
else if (hours) else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins); snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
else else
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins); snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
#endif
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr); display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
// === Second Row: Satellites and Voltage === // === Second Row: Satellites and Voltage ===
@@ -549,6 +625,21 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
} }
#endif #endif
#if defined(M5STACK_UNITC6L)
line += 1;
// === Node Identity ===
int textWidth = 0;
int nameX = 0;
char shortnameble[35];
snprintf(shortnameble, sizeof(shortnameble), "%s",
graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : "");
// === ShortName Centered ===
textWidth = display->getStringWidth(shortnameble);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
#else
if (powerStatus->getHasBattery()) { if (powerStatus->getHasBattery()) {
char batStr[20]; char batStr[20];
int batV = powerStatus->getBatteryVoltageMv() / 1000; int batV = powerStatus->getBatteryVoltageMv() / 1000;
@@ -674,6 +765,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
nameX = (SCREEN_WIDTH - textWidth) / 2; nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], shortnameble); display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
} }
#endif
} }
// Start Functions to write date/time to the screen // Start Functions to write date/time to the screen
@@ -832,6 +924,28 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
// needs to be drawn relative to x and y // needs to be drawn relative to x and y
// draw centered icon left to right and centered above the one line of app text // draw centered icon left to right and centered above the one line of app text
#if defined(M5STACK_UNITC6L)
display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
// Draw region in upper left
if (upperMsg) {
int msgWidth = display->getStringWidth(upperMsg);
int msgX = x + (SCREEN_WIDTH - msgWidth) / 2;
int msgY = y;
display->drawString(msgX, msgY, upperMsg);
}
// Draw version and short name in bottom middle
char buf[25];
snprintf(buf, sizeof(buf), "%s %s", xstr(APP_VERSION_SHORT),
graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : "");
display->drawString(x + getStringCenteredX(buf), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, buf);
screen->forceDisplay();
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
#else
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2, display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
icon_width, icon_height, icon_bits); icon_width, icon_height, icon_bits);
@@ -840,7 +954,6 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
const char *title = "meshtastic.org"; const char *title = "meshtastic.org";
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title); display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
// Draw region in upper left // Draw region in upper left
if (upperMsg) if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg); display->drawString(x + 0, y + 0, upperMsg);
@@ -855,6 +968,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
screen->forceDisplay(); screen->forceDisplay();
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
#endif
} }
// **************************** // ****************************
@@ -879,7 +993,26 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
config.display.heading_bold = false; config.display.heading_bold = false;
const char *displayLine = ""; // Initialize to empty string by default const char *displayLine = ""; // Initialize to empty string by default
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
bool usePhoneGPS = (ourNode && nodeDB->hasValidPosition(ourNode) &&
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED);
if (usePhoneGPS) {
// Phone-provided GPS is active
displayLine = "Phone GPS";
int yOffset = (isHighResolution) ? 3 : 1;
if (isHighResolution) {
NodeListRenderer::drawScaledXBitmap16x16(x, getTextPositions(display)[line] + yOffset - 5, imgSatellite_width,
imgSatellite_height, imgSatellite, display);
} else {
display->drawXbm(x + 1, getTextPositions(display)[line] + yOffset, imgSatellite_width, imgSatellite_height,
imgSatellite);
}
int xOffset = (isHighResolution) ? 6 : 0;
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
} else if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
// GPS disabled / not present
if (config.position.fixed_position) { if (config.position.fixed_position) {
displayLine = "Fixed GPS"; displayLine = "Fixed GPS";
} else { } else {
@@ -896,6 +1029,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
int xOffset = (isHighResolution) ? 6 : 0; int xOffset = (isHighResolution) ? 6 : 0;
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine); display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
} else { } else {
// Onboard GPS
UIRenderer::drawGps(display, 0, getTextPositions(display)[line++], gpsStatus); UIRenderer::drawGps(display, 0, getTextPositions(display)[line++], gpsStatus);
} }
@@ -922,36 +1056,52 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
// If GPS is off, no need to display these parts // If GPS is off, no need to display these parts
if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) { if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
// === Second Row: Last GPS Fix ===
if (gpsStatus->getLastFixMillis() > 0) {
uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix
uint32_t days = delta / 86400;
uint32_t hours = (delta % 86400) / 3600;
uint32_t mins = (delta % 3600) / 60;
uint32_t secs = delta % 60;
// === Second Row: Date === char buf[32];
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); #if defined(USE_EINK)
char datetimeStr[25]; // E-Ink: skip seconds, show only days/hours/mins
bool showTime = false; // set to true for full datetime if (days > 0) {
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime); snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
char fullLine[40]; } else if (hours > 0) {
snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr); snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
display->drawString(0, getTextPositions(display)[line++], fullLine); } else {
snprintf(buf, sizeof(buf), "Last: %um", mins);
}
#else
// Non E-Ink: include seconds where useful
if (days > 0) {
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
} else if (hours > 0) {
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
} else if (mins > 0) {
snprintf(buf, sizeof(buf), "Last: %um %us", mins, secs);
} else {
snprintf(buf, sizeof(buf), "Last: %us", secs);
}
#endif
// === Third Row: Latitude === display->drawString(0, getTextPositions(display)[line++], buf);
char latStr[32];
snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
display->drawString(x, getTextPositions(display)[line++], latStr);
// === Fourth Row: Longitude ===
char lonStr[32];
snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7);
display->drawString(x, getTextPositions(display)[line++], lonStr);
// === Fifth Row: Altitude ===
char DisplayLineTwo[32] = {0};
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET);
} else { } else {
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude()); display->drawString(0, getTextPositions(display)[line++], "Last: ?");
} }
display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
}
// === Third Row: Line 1 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line1");
if (uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC &&
uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS) {
// === Fourth Row: Line 2 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2");
}
}
#if !defined(M5STACK_UNITC6L)
// === Draw Compass if heading is valid === // === Draw Compass if heading is valid ===
if (validHeading) { if (validHeading) {
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic --- // --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
@@ -1034,6 +1184,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
} }
} }
#endif #endif
#endif
} }
#ifdef USERPREFS_OEM_TEXT #ifdef USERPREFS_OEM_TEXT
@@ -1190,7 +1341,6 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setColor(WHITE); display->setColor(WHITE);
} }
} }
// Knock the corners off the square // Knock the corners off the square
display->setColor(BLACK); display->setColor(BLACK);
display->drawRect(rectX, y - 2, 1, 1); display->drawRect(rectX, y - 2, 1, 1);

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "NodeDB.h"
#include "graphics/Screen.h" #include "graphics/Screen.h"
#include "graphics/emotes.h" #include "graphics/emotes.h"
#include <OLEDDisplay.h> #include <OLEDDisplay.h>
@@ -37,7 +38,8 @@ class UIRenderer
// GPS status functions // GPS status functions
static void drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus); static void drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
static void drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus); static void drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus,
const char *mode = "line1");
static void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus); static void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
static void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus); static void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);

View File

@@ -118,8 +118,8 @@ const uint8_t icon_radio[] PROGMEM = {
0xA9 // Row 7: #..#.#.# 0xA9 // Row 7: #..#.#.#
}; };
// 🪙 Memory Icon // 🪙 System Icon
const uint8_t icon_memory[] PROGMEM = { const uint8_t icon_system[] PROGMEM = {
0x24, // Row 0: ..#..#.. 0x24, // Row 0: ..#..#..
0x3C, // Row 1: ..####.. 0x3C, // Row 1: ..####..
0xC3, // Row 2: ##....## 0xC3, // Row 2: ##....##
@@ -287,6 +287,81 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
#define analog_icon_clock_height 8 #define analog_icon_clock_height 8
const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000, const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
0b00100100, 0b01000010, 0b01000010, 0b11111111}; 0b00100100, 0b01000010, 0b01000010, 0b11111111};
#ifdef M5STACK_UNITC6L
#include "img/icon_small.xbm"
#else
#define chirpy_width 38
#define chirpy_height 50
static unsigned char chirpy[] = {
0xfe, 0xff, 0xff, 0xff, 0xdf, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01,
0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01, 0x00,
0x00, 0x00, 0xe0, 0x81, 0xff, 0xff, 0x7f, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xcf, 0x7f,
0xfe, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc,
0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0,
0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1,
0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0xcf, 0x7f, 0xfe, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0x81, 0xff,
0xff, 0x7f, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xc3, 0x00, 0xe0, 0x01, 0x00, 0xc3,
0x00, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x30, 0x03, 0xe0, 0x01, 0xc0, 0x30, 0x03,
0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0,
0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01,
0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xfe, 0xff, 0xff, 0xff, 0xdf};
#define chirpy_width_hirez 76
#define chirpy_height_hirez 100
static unsigned char chirpy_hirez[] = {
0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00,
0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc,
0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03,
0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0,
0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0,
0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff,
0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f,
0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0,
0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff,
0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00,
0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc,
0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03,
0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0,
0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0,
0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff,
0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f,
0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03,
0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0,
0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00,
0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f,
0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c,
0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00,
0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00,
0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc,
0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03,
0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00,
0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0,
0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3};
#define chirpy_small_image_width 8
#define chirpy_small_image_height 8
static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
#include "img/icon.xbm" #include "img/icon.xbm"
#endif
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning"); static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");

View File

@@ -0,0 +1,116 @@
#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
// Chirpy image definitions for M5STACK_UNITC6L compatibility
#define chirpy_width 38
#define chirpy_height 50
static unsigned char chirpy[] = {
0xfe, 0xff, 0xff, 0xff, 0xdf, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01,
0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01, 0x00,
0x00, 0x00, 0xe0, 0x81, 0xff, 0xff, 0x7f, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xcf, 0x7f,
0xfe, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc,
0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0,
0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1,
0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0xcf, 0x7f, 0xfe, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0x81, 0xff,
0xff, 0x7f, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xc3, 0x00, 0xe0, 0x01, 0x00, 0xc3,
0x00, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x30, 0x03, 0xe0, 0x01, 0xc0, 0x30, 0x03,
0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0,
0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01,
0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xfe, 0xff, 0xff, 0xff, 0xdf};
#define chirpy_width_hirez 76
#define chirpy_height_hirez 100
static unsigned char chirpy_hirez[] = {
0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00,
0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc,
0x03, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f,
0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe,
0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff,
0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f,
0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0,
0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f,
0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00,
0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc,
0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f,
0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe,
0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff,
0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f,
0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0,
0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f,
0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00,
0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc,
0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x1f,
0xff, 0xff, 0xff, 0xf8, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff,
0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00,
0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x80, 0x87, 0xe1, 0x01, 0x00,
0x00, 0xfc, 0x03, 0x00, 0x00, 0x80, 0x87, 0xe1, 0x01, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x83, 0xc1, 0x03, 0x00, 0x00,
0xfc, 0x03, 0x00, 0x00, 0xc0, 0x83, 0xc1, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0xfc,
0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xfc, 0x03,
0x00, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1e, 0x00, 0x00, 0xfc, 0x03, 0x00,
0x00, 0x78, 0x00, 0x00, 0x1e, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x78, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x1e,
0x00, 0x00, 0x78, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0x00,
0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xfc, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00,
0xe0, 0x01, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0,
0x03, 0x00, 0xfc, 0x03, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0x07, 0x00, 0xfc, 0x03, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0x07,
0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00,
0xfc, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfc, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfc,
0x03, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfc, 0x03, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfc, 0x03,
0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfc, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfc, 0x03, 0x00,
0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xfc, 0x03, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xfc, 0x03, 0x80, 0x07,
0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xfc, 0x03, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xfc, 0x03, 0xc0, 0x03, 0x00,
0x00, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0x00,
0x00, 0x00, 0x80, 0x07, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0xfc, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0f, 0xfc, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3
};
#define chirpy_small_image_width 8
#define chirpy_small_image_height 8
static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};

View File

@@ -41,78 +41,78 @@ void tftSetup(void)
PacketAPI::create(PacketServer::init()); PacketAPI::create(PacketServer::init());
deviceScreen->init(new PacketClient); deviceScreen->init(new PacketClient);
#else #else
if (settingsMap[displayPanel] != no_screen) { if (portduino_config.displayPanel != no_screen) {
DisplayDriverConfig displayConfig; DisplayDriverConfig displayConfig;
static char *panels[] = {"NOSCREEN", "X11", "FB", "ST7789", "ST7735", "ST7735S", static char *panels[] = {"NOSCREEN", "X11", "FB", "ST7789", "ST7735", "ST7735S",
"ST7796", "ILI9341", "ILI9342", "ILI9486", "ILI9488", "HX8357D"}; "ST7796", "ILI9341", "ILI9342", "ILI9486", "ILI9488", "HX8357D"};
static char *touch[] = {"NOTOUCH", "XPT2046", "STMPE610", "GT911", "FT5x06"}; static char *touch[] = {"NOTOUCH", "XPT2046", "STMPE610", "GT911", "FT5x06"};
#if defined(USE_X11) #if defined(USE_X11)
if (settingsMap[displayPanel] == x11) { if (portduino_config.displayPanel == x11) {
if (settingsMap[displayWidth] && settingsMap[displayHeight]) if (portduino_config.displayWidth && portduino_config.displayHeight)
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::X11, (uint16_t)settingsMap[displayWidth], displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::X11, (uint16_t)portduino_config.displayWidth,
(uint16_t)settingsMap[displayHeight]); (uint16_t)portduino_config.displayHeight);
else else
displayConfig.device(DisplayDriverConfig::device_t::X11); displayConfig.device(DisplayDriverConfig::device_t::X11);
} else } else
#elif defined(USE_FRAMEBUFFER) #elif defined(USE_FRAMEBUFFER)
if (settingsMap[displayPanel] == fb) { if (portduino_config.displayPanel == fb) {
if (settingsMap[displayWidth] && settingsMap[displayHeight]) if (portduino_config.displayWidth && portduino_config.displayHeight)
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::FB, (uint16_t)settingsMap[displayWidth], displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::FB, (uint16_t)portduino_config.displayWidth,
(uint16_t)settingsMap[displayHeight]); (uint16_t)portduino_config.displayHeight);
else else
displayConfig.device(DisplayDriverConfig::device_t::FB); displayConfig.device(DisplayDriverConfig::device_t::FB);
} else } else
#endif #endif
{ {
displayConfig.device(DisplayDriverConfig::device_t::CUSTOM_TFT) displayConfig.device(DisplayDriverConfig::device_t::CUSTOM_TFT)
.panel(DisplayDriverConfig::panel_config_t{.type = panels[settingsMap[displayPanel]], .panel(DisplayDriverConfig::panel_config_t{.type = panels[portduino_config.displayPanel],
.panel_width = (uint16_t)settingsMap[displayWidth], .panel_width = (uint16_t)portduino_config.displayWidth,
.panel_height = (uint16_t)settingsMap[displayHeight], .panel_height = (uint16_t)portduino_config.displayHeight,
.rotation = (bool)settingsMap[displayRotate], .rotation = (bool)portduino_config.displayRotate,
.pin_cs = (int16_t)settingsMap[displayCS], .pin_cs = (int16_t)portduino_config.displayCS.pin,
.pin_rst = (int16_t)settingsMap[displayReset], .pin_rst = (int16_t)portduino_config.displayReset.pin,
.offset_x = (uint16_t)settingsMap[displayOffsetX], .offset_x = (uint16_t)portduino_config.displayOffsetX,
.offset_y = (uint16_t)settingsMap[displayOffsetY], .offset_y = (uint16_t)portduino_config.displayOffsetY,
.offset_rotation = (uint8_t)settingsMap[displayOffsetRotate], .offset_rotation = (uint8_t)portduino_config.displayOffsetRotate,
.invert = settingsMap[displayInvert] ? true : false, .invert = portduino_config.displayInvert ? true : false,
.rgb_order = (bool)settingsMap[displayRGBOrder], .rgb_order = (bool)portduino_config.displayRGBOrder,
.dlen_16bit = settingsMap[displayPanel] == ili9486 || .dlen_16bit = portduino_config.displayPanel == ili9486 ||
settingsMap[displayPanel] == ili9488}) portduino_config.displayPanel == ili9488})
.bus(DisplayDriverConfig::bus_config_t{.freq_write = (uint32_t)settingsMap[displayBusFrequency], .bus(DisplayDriverConfig::bus_config_t{.freq_write = (uint32_t)portduino_config.displayBusFrequency,
.freq_read = 16000000, .freq_read = 16000000,
.spi{.pin_dc = (int8_t)settingsMap[displayDC], .spi{.pin_dc = (int8_t)portduino_config.displayDC.pin,
.use_lock = true, .use_lock = true,
.spi_host = (uint16_t)settingsMap[displayspidev]}}) .spi_host = (uint16_t)portduino_config.display_spi_dev_int}})
.input(DisplayDriverConfig::input_config_t{.keyboardDevice = settingsStrings[keyboardDevice], .input(DisplayDriverConfig::input_config_t{.keyboardDevice = portduino_config.keyboardDevice,
.pointerDevice = settingsStrings[pointerDevice]}) .pointerDevice = portduino_config.pointerDevice})
.light(DisplayDriverConfig::light_config_t{.pin_bl = (int16_t)settingsMap[displayBacklight], .light(DisplayDriverConfig::light_config_t{.pin_bl = (int16_t)portduino_config.displayBacklight.pin,
.pwm_channel = (int8_t)settingsMap[displayBacklightPWMChannel], .pwm_channel = (int8_t)portduino_config.displayBacklightPWMChannel.pin,
.invert = (bool)settingsMap[displayBacklightInvert]}); .invert = (bool)portduino_config.displayBacklightInvert});
if (settingsMap[touchscreenI2CAddr] == -1) { if (portduino_config.touchscreenI2CAddr == -1) {
displayConfig.touch( displayConfig.touch(
DisplayDriverConfig::touch_config_t{.type = touch[settingsMap[touchscreenModule]], DisplayDriverConfig::touch_config_t{.type = touch[portduino_config.touchscreenModule],
.freq = (uint32_t)settingsMap[touchscreenBusFrequency], .freq = (uint32_t)portduino_config.touchscreenBusFrequency,
.pin_int = (int16_t)settingsMap[touchscreenIRQ], .pin_int = (int16_t)portduino_config.touchscreenIRQ.pin,
.offset_rotation = (uint8_t)settingsMap[touchscreenRotate], .offset_rotation = (uint8_t)portduino_config.touchscreenRotate,
.spi{ .spi{
.spi_host = (int8_t)settingsMap[touchscreenspidev], .spi_host = (int8_t)portduino_config.touchscreen_spi_dev_int,
}, },
.pin_cs = (int16_t)settingsMap[touchscreenCS]}); .pin_cs = (int16_t)portduino_config.touchscreenCS.pin});
} else { } else {
displayConfig.touch(DisplayDriverConfig::touch_config_t{ displayConfig.touch(DisplayDriverConfig::touch_config_t{
.type = touch[settingsMap[touchscreenModule]], .type = touch[portduino_config.touchscreenModule],
.freq = (uint32_t)settingsMap[touchscreenBusFrequency], .freq = (uint32_t)portduino_config.touchscreenBusFrequency,
.x_min = 0, .x_min = 0,
.x_max = .x_max = (int16_t)((portduino_config.touchscreenRotate & 1 ? portduino_config.displayWidth
(int16_t)((settingsMap[touchscreenRotate] & 1 ? settingsMap[displayWidth] : settingsMap[displayHeight]) - : portduino_config.displayHeight) -
1), 1),
.y_min = 0, .y_min = 0,
.y_max = .y_max = (int16_t)((portduino_config.touchscreenRotate & 1 ? portduino_config.displayHeight
(int16_t)((settingsMap[touchscreenRotate] & 1 ? settingsMap[displayHeight] : settingsMap[displayWidth]) - : portduino_config.displayWidth) -
1), 1),
.pin_int = (int16_t)settingsMap[touchscreenIRQ], .pin_int = (int16_t)portduino_config.touchscreenIRQ.pin,
.offset_rotation = (uint8_t)settingsMap[touchscreenRotate], .offset_rotation = (uint8_t)portduino_config.touchscreenRotate,
.i2c{.i2c_addr = (uint8_t)settingsMap[touchscreenI2CAddr]}}); .i2c{.i2c_addr = (uint8_t)portduino_config.touchscreenI2CAddr}});
} }
} }
deviceScreen = &DeviceScreen::create(&displayConfig); deviceScreen = &DeviceScreen::create(&displayConfig);

View File

@@ -188,7 +188,7 @@ void ExpressLRSFiveWay::determineAction(KeyType key, PressLength length)
// Feed input to the canned messages module // Feed input to the canned messages module
void ExpressLRSFiveWay::sendKey(input_broker_event key) void ExpressLRSFiveWay::sendKey(input_broker_event key)
{ {
InputEvent e; InputEvent e = {};
e.source = inputSourceName; e.source = inputSourceName;
e.inputEvent = key; e.inputEvent = key;
notifyObservers(&e); notifyObservers(&e);

View File

@@ -33,9 +33,9 @@ int32_t LinuxInput::runOnce()
{ {
if (firstTime) { if (firstTime) {
if (settingsStrings[keyboardDevice] == "") if (portduino_config.keyboardDevice == "")
return disable(); return disable();
fd = open(settingsStrings[keyboardDevice].c_str(), O_RDWR); fd = open(portduino_config.keyboardDevice.c_str(), O_RDWR);
if (fd < 0) if (fd < 0)
return disable(); return disable();
ret = ioctl(fd, EVIOCGRAB, (void *)1); ret = ioctl(fd, EVIOCGRAB, (void *)1);
@@ -73,7 +73,7 @@ int32_t LinuxInput::runOnce()
int rd = read(events[i].data.fd, ev, sizeof(ev)); int rd = read(events[i].data.fd, ev, sizeof(ev));
assert(rd > ((signed int)sizeof(struct input_event))); assert(rd > ((signed int)sizeof(struct input_event)));
for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) { for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) {
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName; e.source = this->_originName;
e.kbchar = 0; e.kbchar = 0;

View File

@@ -43,7 +43,7 @@ void RotaryEncoderInterruptBase::init(
int32_t RotaryEncoderInterruptBase::runOnce() int32_t RotaryEncoderInterruptBase::runOnce()
{ {
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName; e.source = this->_originName;
@@ -120,7 +120,7 @@ RotaryEncoderInterruptBaseStateType RotaryEncoderInterruptBase::intHandler(bool
// Logic to prevent bouncing. // Logic to prevent bouncing.
newState = ROTARY_EVENT_CLEARED; newState = ROTARY_EVENT_CLEARED;
} }
setIntervalFromNow(50); // TODO: this modifies a non-volatile variable! setIntervalFromNow(ROTARY_DELAY); // TODO: this modifies a non-volatile variable!
return newState; return newState;
} }

View File

@@ -49,7 +49,7 @@ bool SeesawRotary::init()
int32_t SeesawRotary::runOnce() int32_t SeesawRotary::runOnce()
{ {
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
bool currentlyPressed = !ss.digitalRead(SS_SWITCH); bool currentlyPressed = !ss.digitalRead(SS_SWITCH);

View File

@@ -29,7 +29,7 @@ SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name)
void SerialKeyboard::erase() void SerialKeyboard::erase()
{ {
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_BACK; e.inputEvent = INPUT_BROKER_BACK;
e.kbchar = 0x08; e.kbchar = 0x08;
e.source = this->_originName; e.source = this->_originName;
@@ -80,7 +80,7 @@ int32_t SerialKeyboard::runOnce()
if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but
// shouldn't be a limitation // shouldn't be a limitation
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName; e.source = this->_originName;
// SELECT OR SEND OR CANCEL EVENT // SELECT OR SEND OR CANCEL EVENT

View File

@@ -18,7 +18,7 @@ TouchScreenImpl1::TouchScreenImpl1(uint16_t width, uint16_t height, bool (*getTo
void TouchScreenImpl1::init() void TouchScreenImpl1::init()
{ {
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (settingsMap[touchscreenModule]) { if (portduino_config.touchscreenModule) {
TouchScreenBase::init(true); TouchScreenBase::init(true);
inputBroker->registerSource(this); inputBroker->registerSource(this);
} else { } else {
@@ -47,7 +47,7 @@ bool TouchScreenImpl1::getTouch(int16_t &x, int16_t &y)
*/ */
void TouchScreenImpl1::onEvent(const TouchEvent &event) void TouchScreenImpl1::onEvent(const TouchEvent &event)
{ {
InputEvent e; InputEvent e = {};
e.source = event.source; e.source = event.source;
e.kbchar = 0; e.kbchar = 0;
e.touchX = event.x; e.touchX = event.x;

View File

@@ -48,7 +48,7 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
int32_t TrackballInterruptBase::runOnce() int32_t TrackballInterruptBase::runOnce()
{ {
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball #if defined(T_DECK) // T-deck gets a super-simple debounce on trackball
if (this->action == TB_ACTION_PRESSED) { if (this->action == TB_ACTION_PRESSED) {

View File

@@ -6,7 +6,7 @@
#ifndef TB_DIRECTION #ifndef TB_DIRECTION
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
#include "PortduinoGlue.h" #include "PortduinoGlue.h"
#define TB_DIRECTION (PinStatus) settingsMap[tbDirection] #define TB_DIRECTION (PinStatus) portduino_config.lora_usb_vid
#else #else
#define TB_DIRECTION RISING #define TB_DIRECTION RISING
#endif #endif

View File

@@ -40,7 +40,7 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
int32_t UpDownInterruptBase::runOnce() int32_t UpDownInterruptBase::runOnce()
{ {
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
unsigned long now = millis(); unsigned long now = millis();
if (this->action == UPDOWN_ACTION_PRESSED) { if (this->action == UPDOWN_ACTION_PRESSED) {

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

@@ -90,7 +90,7 @@ int32_t KbI2cBase::runOnce()
while (keyCount--) { while (keyCount--) {
const BBQ10Keyboard::KeyEvent key = Q10keyboard.keyEvent(); const BBQ10Keyboard::KeyEvent key = Q10keyboard.keyEvent();
if ((key.key != 0x00) && (key.state == BBQ10Keyboard::StateRelease)) { if ((key.key != 0x00) && (key.state == BBQ10Keyboard::StateRelease)) {
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName; e.source = this->_originName;
switch (key.key) { switch (key.key) {
@@ -187,7 +187,7 @@ int32_t KbI2cBase::runOnce()
} }
case 0x37: { // MPR121 case 0x37: { // MPR121
MPRkeyboard.trigger(); MPRkeyboard.trigger();
InputEvent e; InputEvent e = {};
while (MPRkeyboard.hasEvent()) { while (MPRkeyboard.hasEvent()) {
char nextEvent = MPRkeyboard.dequeueEvent(); char nextEvent = MPRkeyboard.dequeueEvent();
@@ -250,7 +250,7 @@ int32_t KbI2cBase::runOnce()
} }
case 0x84: { // Adafruit TCA8418 case 0x84: { // Adafruit TCA8418
TCAKeyboard.trigger(); TCAKeyboard.trigger();
InputEvent e; InputEvent e = {};
while (TCAKeyboard.hasEvent()) { while (TCAKeyboard.hasEvent()) {
char nextEvent = TCAKeyboard.dequeueEvent(); char nextEvent = TCAKeyboard.dequeueEvent();
e.inputEvent = INPUT_BROKER_ANYKEY; e.inputEvent = INPUT_BROKER_ANYKEY;
@@ -350,7 +350,7 @@ int32_t KbI2cBase::runOnce()
} }
if (PrintDataBuf != 0) { if (PrintDataBuf != 0) {
LOG_DEBUG("RAK14004 key 0x%x pressed", PrintDataBuf); LOG_DEBUG("RAK14004 key 0x%x pressed", PrintDataBuf);
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_MATRIXKEY; e.inputEvent = INPUT_BROKER_MATRIXKEY;
e.source = this->_originName; e.source = this->_originName;
e.kbchar = PrintDataBuf; e.kbchar = PrintDataBuf;
@@ -365,7 +365,7 @@ int32_t KbI2cBase::runOnce()
if (i2cBus->available()) { if (i2cBus->available()) {
char c = i2cBus->read(); char c = i2cBus->read();
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName; e.source = this->_originName;
switch (c) { switch (c) {

View File

@@ -72,7 +72,7 @@ int32_t KbMatrixBase::runOnce()
if (key != 0) { if (key != 0) {
LOG_DEBUG("Key 0x%x pressed", key); LOG_DEBUG("Key 0x%x pressed", key);
// reset shift now that we have a keypress // reset shift now that we have a keypress
InputEvent e; InputEvent e = {};
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName; e.source = this->_originName;
switch (key) { switch (key) {

View File

@@ -385,10 +385,9 @@ void setup()
io.digitalWrite(EXPANDS_GPIO_EN, HIGH); io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
io.pinMode(EXPANDS_SD_PULLEN, INPUT); io.pinMode(EXPANDS_SD_PULLEN, INPUT);
#endif #endif
concurrency::hasBeenSetup = true; concurrency::hasBeenSetup = true;
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0); SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
#else #else
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
#endif #endif
@@ -533,9 +532,9 @@ void setup()
#elif defined(I2C_SDA) && !defined(ARCH_RP2040) #elif defined(I2C_SDA) && !defined(ARCH_RP2040)
Wire.begin(I2C_SDA, I2C_SCL); Wire.begin(I2C_SDA, I2C_SCL);
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
if (settingsStrings[i2cdev] != "") { if (portduino_config.i2cdev != "") {
LOG_INFO("Use %s as I2C device", settingsStrings[i2cdev].c_str()); LOG_INFO("Use %s as I2C device", portduino_config.i2cdev.c_str());
Wire.begin(settingsStrings[i2cdev].c_str()); Wire.begin(portduino_config.i2cdev.c_str());
} else { } else {
LOG_INFO("No I2C device configured, Skip"); LOG_INFO("No I2C device configured, Skip");
} }
@@ -544,6 +543,12 @@ void setup()
#endif #endif
#endif #endif
#if defined(M5STACK_UNITC6L)
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, 1);
c6l_init();
#endif
#ifdef PIN_LCD_RESET #ifdef PIN_LCD_RESET
// FIXME - move this someplace better, LCD is at address 0x3F // FIXME - move this someplace better, LCD is at address 0x3F
pinMode(PIN_LCD_RESET, OUTPUT); pinMode(PIN_LCD_RESET, OUTPUT);
@@ -581,7 +586,7 @@ void setup()
#if defined(I2C_SDA) #if defined(I2C_SDA)
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
if (settingsStrings[i2cdev] != "") { if (portduino_config.i2cdev != "") {
LOG_INFO("Scan for i2c devices"); LOG_INFO("Scan for i2c devices");
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
} }
@@ -854,7 +859,7 @@ void setup()
SPI.begin(false); SPI.begin(false);
#endif // HW_SPI1_DEVICE #endif // HW_SPI1_DEVICE
#elif ARCH_PORTDUINO #elif ARCH_PORTDUINO
if (settingsStrings[spidev] != "ch341") { if (portduino_config.lora_spi_dev != "ch341") {
SPI.begin(); SPI.begin();
} }
#elif !defined(ARCH_ESP32) // ARCH_RP2040 #elif !defined(ARCH_ESP32) // ARCH_RP2040
@@ -877,10 +882,11 @@ void setup()
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(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); screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) && if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
screen = new graphics::Screen(screen_found, screen_model, screen_geometry); screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
} }
@@ -981,13 +987,13 @@ void setup()
#endif #endif
#if defined(ARCH_PORTDUINO) #if defined(ARCH_PORTDUINO)
if (settingsMap.count(userButtonPin) != 0 && settingsMap[userButtonPin] != RADIOLIB_NC) { if (portduino_config.userButtonPin.enabled) {
LOG_DEBUG("Use GPIO%02d for button", settingsMap[userButtonPin]); LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin);
UserButtonThread = new ButtonThread("UserButton"); UserButtonThread = new ButtonThread("UserButton");
if (screen) { if (screen) {
ButtonConfig config; ButtonConfig config;
config.pinNumber = (uint8_t)settingsMap[userButtonPin]; config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin;
config.activeLow = true; config.activeLow = true;
config.activePullup = true; config.activePullup = true;
config.pullupSense = INPUT_PULLUP; config.pullupSense = INPUT_PULLUP;
@@ -1140,11 +1146,12 @@ void setup()
// Don't call screen setup until after nodedb is setup (because we need // Don't call screen setup until after nodedb is setup (because we need
// the current region name) // the current region name)
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_SPISSD1306)
if (screen) if (screen)
screen->setup(); screen->setup();
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) && if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
screen->setup(); screen->setup();
} }
@@ -1160,15 +1167,10 @@ void setup()
#endif #endif
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
const struct {
configNames cfgName;
std::string strName;
} loraModules[] = {{use_rf95, "RF95"}, {use_sx1262, "sx1262"}, {use_sx1268, "sx1268"}, {use_sx1280, "sx1280"},
{use_lr1110, "lr1110"}, {use_lr1120, "lr1120"}, {use_lr1121, "lr1121"}, {use_llcc68, "LLCC68"}};
// as one can't use a function pointer to the class constructor: // as one can't use a function pointer to the class constructor:
auto loraModuleInterface = [](configNames cfgName, LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) { RADIOLIB_PIN_TYPE busy) {
switch (cfgName) { switch (portduino_config.lora_module) {
case use_rf95: case use_rf95:
return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy); return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy);
case use_sx1262: case use_sx1262:
@@ -1185,31 +1187,34 @@ void setup()
return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy); return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy);
case use_llcc68: case use_llcc68:
return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy); return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy);
case use_simradio:
return (RadioInterface *)new SimRadio;
default: default:
assert(0); // shouldn't happen assert(0); // shouldn't happen
return (RadioInterface *)nullptr; return (RadioInterface *)nullptr;
} }
}; };
for (auto &loraModule : loraModules) {
if (settingsMap[loraModule.cfgName] && !rIf) { LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(),
LOG_DEBUG("Activate %s radio on SPI port %s", loraModule.strName.c_str(), settingsStrings[spidev].c_str()); portduino_config.lora_spi_dev.c_str());
if (settingsStrings[spidev] == "ch341") { if (portduino_config.lora_spi_dev == "ch341") {
RadioLibHAL = ch341Hal; RadioLibHAL = ch341Hal;
} else { } else {
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
}
rIf = loraModuleInterface(loraModule.cfgName, (LockingArduinoHal *)RadioLibHAL, settingsMap[cs_pin],
settingsMap[irq_pin], settingsMap[reset_pin], settingsMap[busy_pin]);
if (!rIf->init()) {
LOG_WARN("No %s radio", loraModule.strName.c_str());
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
LOG_INFO("%s init success", loraModule.strName.c_str());
}
}
} }
rIf =
loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin,
portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin);
if (!rIf->init()) {
LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str());
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str());
}
#elif defined(HW_SPI1_DEVICE) #elif defined(HW_SPI1_DEVICE)
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings); LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings);
#else // HW_SPI1_DEVICE #else // HW_SPI1_DEVICE
@@ -1231,20 +1236,6 @@ void setup()
} }
#endif #endif
#if defined(ARCH_PORTDUINO)
if (!rIf) {
rIf = new SimRadio;
if (!rIf->init()) {
LOG_WARN("No simulated radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("Use SIMULATED radio!");
radioType = SIM_RADIO;
}
}
#endif
#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 #if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1);
@@ -1455,7 +1446,7 @@ void setup()
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
#if __has_include(<ulfius.h>) #if __has_include(<ulfius.h>)
if (settingsMap[webserverport] != -1) { if (portduino_config.webserverport != -1) {
piwebServerThread = new PiWebServerThread(); piwebServerThread = new PiWebServerThread();
std::atexit([] { delete piwebServerThread; }); std::atexit([] { delete piwebServerThread; });
} }

View File

@@ -88,4 +88,16 @@ uint32_t MemGet::getPsramSize()
#else #else
return 0; return 0;
#endif #endif
}
void displayPercentHeapFree()
{
uint32_t freeHeap = memGet.getFreeHeap();
uint32_t totalHeap = memGet.getHeapSize();
if (totalHeap == 0 || totalHeap == UINT32_MAX) {
LOG_INFO("Heap size unavailable");
return;
}
int percent = (int)((freeHeap * 100) / totalHeap);
LOG_INFO("Heap free: %d%% (%u/%u bytes)", percent, freeHeap, totalHeap);
} }

View File

@@ -423,6 +423,33 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
} }
} }
bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash)
{
// Iterate all known presets
for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
++preset) {
const char *name =
DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false, false);
if (!name)
continue;
if (strcmp(name, "Invalid") == 0)
continue; // skip invalid placeholder
uint8_t h = xorHash((const uint8_t *)name, strlen(name));
// Expand default PSK alias 1 to actual bytes and xor into hash
uint8_t tmp = h ^ xorHash(defaultpsk, sizeof(defaultpsk));
if (tmp == channelHash) {
// Set crypto to defaultpsk and report success
CryptoKey k;
memcpy(k.bytes, defaultpsk, sizeof(defaultpsk));
k.length = sizeof(defaultpsk);
crypto->setKey(k);
LOG_INFO("Matched default preset '%s' for hash 0x%x; set default PSK", name, channelHash);
return true;
}
}
return false;
}
/** Given a channel index setup crypto for encoding that channel (or the primary channel if that channel is unsecured) /** Given a channel index setup crypto for encoding that channel (or the primary channel if that channel is unsecured)
* *
* This method is called before encoding outbound packets * This method is called before encoding outbound packets

View File

@@ -94,6 +94,8 @@ class Channels
bool ensureLicensedOperation(); bool ensureLicensedOperation();
bool setDefaultPresetCryptoForHash(ChannelHash channelHash);
private: private:
/** Given a channel index, change to use the crypto key specified by that index /** Given a channel index, change to use the crypto key specified by that index
* *

View File

@@ -43,12 +43,30 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
return Router::shouldFilterReceived(p); return Router::shouldFilterReceived(p);
} }
bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
{
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
// ROUTER, REPEATER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
// even if we've heard another station rebroadcast it already.
return false;
}
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
// CLIENT_BASE: if the packet is from or to a favorited node,
// we should act like a ROUTER and should never cancel a rebroadcast (i.e. we should always rebroadcast),
// even if we've heard another station rebroadcast it already.
return !nodeDB->isFromOrToFavoritedNode(*p);
}
// All other roles (such as CLIENT) should cancel a rebroadcast if they hear another station's rebroadcast.
return true;
}
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p) void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
{ {
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA && roleAllowsCancelingDupe(p)) {
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE &&
p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater! // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
// But only LoRa packets should be able to trigger this. // But only LoRa packets should be able to trigger this.
if (Router::cancelSending(p->from, p->id)) if (Router::cancelSending(p->from, p->id))

View File

@@ -59,6 +59,10 @@ class FloodingRouter : public Router
*/ */
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override; virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
// Return false for roles like ROUTER or REPEATER which should always rebroadcast even when we've heard another rebroadcast of
// the same packet
bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
/* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */ /* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */
void perhapsCancelDupe(const meshtastic_MeshPacket *p); void perhapsCancelDupe(const meshtastic_MeshPacket *p);

View File

@@ -21,7 +21,7 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not // Particular boards might define a different max power based on what their hardware can do, default to max power output if not
// specified (may be dangerous if using external PA and LR11x0 power config forgotten) // specified (may be dangerous if using external PA and LR11x0 power config forgotten)
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
#define LR1110_MAX_POWER settingsMap[lr1110_max_power] #define LR1110_MAX_POWER portduino_config.lr1110_max_power
#endif #endif
#ifndef LR1110_MAX_POWER #ifndef LR1110_MAX_POWER
#define LR1110_MAX_POWER 22 #define LR1110_MAX_POWER 22
@@ -30,7 +30,7 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
// the 2.4G part maxes at 13dBm // the 2.4G part maxes at 13dBm
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
#define LR1120_MAX_POWER settingsMap[lr1120_max_power] #define LR1120_MAX_POWER portduino_config.lr1120_max_power
#endif #endif
#ifndef LR1120_MAX_POWER #ifndef LR1120_MAX_POWER
#define LR1120_MAX_POWER 13 #define LR1120_MAX_POWER 13
@@ -55,7 +55,7 @@ template <typename T> bool LR11x0Interface<T>::init()
#endif #endif
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
float tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000; float tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
// FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE // FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE
#elif !defined(LR11X0_DIO3_TCXO_VOLTAGE) #elif !defined(LR11X0_DIO3_TCXO_VOLTAGE)
float tcxoVoltage = float tcxoVoltage =

View File

@@ -6,6 +6,7 @@
#include <memory> #include <memory>
#include "PointerQueue.h" #include "PointerQueue.h"
#include "configuration.h" // For LOG_WARN, LOG_DEBUG, LOG_HEAP
template <class T> class Allocator template <class T> class Allocator
{ {
@@ -14,13 +15,14 @@ template <class T> class Allocator
Allocator() : deleter([this](T *p) { this->release(p); }) {} Allocator() : deleter([this](T *p) { this->release(p); }) {}
virtual ~Allocator() {} virtual ~Allocator() {}
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available /// Return a queable object which has been prefilled with zeros. Return nullptr if no buffer is available
/// Note: this method is safe to call from regular OR ISR code /// Note: this method is safe to call from regular OR ISR code
T *allocZeroed() T *allocZeroed()
{ {
T *p = allocZeroed(0); T *p = allocZeroed(0);
if (!p) {
assert(p); // FIXME panic instead LOG_WARN("Failed to allocate zeroed memory");
}
return p; return p;
} }
@@ -39,10 +41,12 @@ template <class T> class Allocator
T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY) T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
{ {
T *p = alloc(maxWait); T *p = alloc(maxWait);
assert(p); if (!p) {
LOG_WARN("Failed to allocate memory for copy");
return nullptr;
}
if (p) *p = src;
*p = src;
return p; return p;
} }
@@ -83,7 +87,11 @@ template <class T> class MemoryDynamic : public Allocator<T>
/// Return a buffer for use by others /// Return a buffer for use by others
virtual void release(T *p) override virtual void release(T *p) override
{ {
assert(p); if (p == nullptr)
return;
LOG_HEAP("Freeing 0x%x", p);
free(p); free(p);
} }
@@ -96,3 +104,58 @@ template <class T> class MemoryDynamic : public Allocator<T>
return p; return p;
} }
}; };
/**
* A static memory pool that uses a fixed buffer instead of heap allocation
*/
template <class T, int MaxSize> class MemoryPool : public Allocator<T>
{
private:
T pool[MaxSize];
bool used[MaxSize];
public:
MemoryPool() : pool{}, used{}
{
// Arrays are now zero-initialized by member initializer list
// pool array: all elements are default-constructed (zero for POD types)
// used array: all elements are false (zero-initialized)
}
/// Return a buffer for use by others
virtual void release(T *p) override
{
if (!p) {
LOG_DEBUG("Failed to release memory, pointer is null");
return;
}
// Find the index of this pointer in our pool
int index = p - pool;
if (index >= 0 && index < MaxSize) {
assert(used[index]); // Should be marked as used
used[index] = false;
LOG_HEAP("Released static pool item %d at 0x%x", index, p);
} else {
LOG_WARN("Pointer 0x%x not from our pool!", p);
}
}
protected:
// Alloc some storage from our static pool
virtual T *alloc(TickType_t maxWait) override
{
// Find first free slot
for (int i = 0; i < MaxSize; i++) {
if (!used[i]) {
used[i] = true;
LOG_HEAP("Allocated static pool item %d at 0x%x", i, &pool[i]);
return &pool[i];
}
}
// No free slots available - return nullptr instead of asserting
LOG_WARN("No free slots available in static memory pool!");
return nullptr;
}
};

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 // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
auto ourNodeNum = nodeDB->getNodeNum(); auto ourNodeNum = nodeDB->getNodeNum();
bool toUs = isBroadcast(mp.to) || isToUs(&mp); bool toUs = isBroadcast(mp.to) || isToUs(&mp);
bool fromUs = mp.from == ourNodeNum;
for (auto i = modules->begin(); i != modules->end(); ++i) { for (auto i = modules->begin(); i != modules->end(); ++i) {
auto &pi = **i; auto &pi = **i;

View File

@@ -46,11 +46,14 @@ the new node can build its node db)
MeshService *service; MeshService *service;
static MemoryDynamic<meshtastic_MqttClientProxyMessage> staticMqttClientProxyMessagePool; #define MAX_MQTT_PROXY_MESSAGES 16
static MemoryPool<meshtastic_MqttClientProxyMessage, MAX_MQTT_PROXY_MESSAGES> staticMqttClientProxyMessagePool;
static MemoryDynamic<meshtastic_QueueStatus> staticQueueStatusPool; #define MAX_QUEUE_STATUS 4
static MemoryPool<meshtastic_QueueStatus, MAX_QUEUE_STATUS> staticQueueStatusPool;
static MemoryDynamic<meshtastic_ClientNotification> staticClientNotificationPool; #define MAX_CLIENT_NOTIFICATIONS 4
static MemoryPool<meshtastic_ClientNotification, MAX_CLIENT_NOTIFICATIONS> staticClientNotificationPool;
Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool; Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool;
@@ -61,8 +64,10 @@ Allocator<meshtastic_QueueStatus> &queueStatusPool = staticQueueStatusPool;
#include "Router.h" #include "Router.h"
MeshService::MeshService() MeshService::MeshService()
: toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE), #ifdef ARCH_PORTDUINO
toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2) : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_QUEUESTATUS_TOPHONE),
toPhoneMqttProxyQueue(MAX_RX_MQTTPROXY_TOPHONE), toPhoneClientNotificationQueue(MAX_RX_NOTIFICATION_TOPHONE)
#endif
{ {
lastQueueStatus = {0, 0, 16, 0}; lastQueueStatus = {0, 0, 16, 0};
} }
@@ -191,8 +196,10 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
// (so we update our nodedb for the local node) // (so we update our nodedb for the local node)
// Send the packet into the mesh // Send the packet into the mesh
DEBUG_HEAP_BEFORE;
sendToMesh(packetPool.allocCopy(p), RX_SRC_USER); auto a = packetPool.allocCopy(p);
DEBUG_HEAP_AFTER("MeshService::handleToRadio", a);
sendToMesh(a, RX_SRC_USER);
bool loopback = false; // if true send any packet the phone sends back itself (for testing) bool loopback = false; // if true send any packet the phone sends back itself (for testing)
if (loopback) { if (loopback) {
@@ -248,7 +255,11 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh
} }
if ((res == ERRNO_OK || res == ERRNO_SHOULD_RELEASE) && ccToPhone) { // Check if p is not released in case it couldn't be sent if ((res == ERRNO_OK || res == ERRNO_SHOULD_RELEASE) && ccToPhone) { // Check if p is not released in case it couldn't be sent
sendToPhone(packetPool.allocCopy(*p)); DEBUG_HEAP_BEFORE;
auto a = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("MeshService::sendToMesh", a);
sendToPhone(a);
} }
// Router may ask us to release the packet if it wasn't sent // Router may ask us to release the packet if it wasn't sent

View File

@@ -9,7 +9,12 @@
#include "MeshRadio.h" #include "MeshRadio.h"
#include "MeshTypes.h" #include "MeshTypes.h"
#include "Observer.h" #include "Observer.h"
#ifdef ARCH_PORTDUINO
#include "PointerQueue.h" #include "PointerQueue.h"
#else
#include "StaticPointerQueue.h"
#endif
#include "mesh-pb-constants.h"
#if defined(ARCH_PORTDUINO) #if defined(ARCH_PORTDUINO)
#include "../platform/portduino/SimRadio.h" #include "../platform/portduino/SimRadio.h"
#endif #endif
@@ -37,16 +42,32 @@ class MeshService
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure /// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
/// we never hang because android hasn't been there in a while /// we never hang because android hasn't been there in a while
/// FIXME - save this to flash on deep sleep /// FIXME - save this to flash on deep sleep
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_MeshPacket> toPhoneQueue; PointerQueue<meshtastic_MeshPacket> toPhoneQueue;
#else
StaticPointerQueue<meshtastic_MeshPacket, MAX_RX_TOPHONE> toPhoneQueue;
#endif
// keep list of QueueStatus packets to be send to the phone // keep list of QueueStatus packets to be send to the phone
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_QueueStatus> toPhoneQueueStatusQueue; PointerQueue<meshtastic_QueueStatus> toPhoneQueueStatusQueue;
#else
StaticPointerQueue<meshtastic_QueueStatus, MAX_RX_QUEUESTATUS_TOPHONE> toPhoneQueueStatusQueue;
#endif
// keep list of MqttClientProxyMessages to be send to the client for delivery // keep list of MqttClientProxyMessages to be send to the client for delivery
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_MqttClientProxyMessage> toPhoneMqttProxyQueue; PointerQueue<meshtastic_MqttClientProxyMessage> toPhoneMqttProxyQueue;
#else
StaticPointerQueue<meshtastic_MqttClientProxyMessage, MAX_RX_MQTTPROXY_TOPHONE> toPhoneMqttProxyQueue;
#endif
// keep list of ClientNotifications to be send to the client (phone) // keep list of ClientNotifications to be send to the client (phone)
#ifdef ARCH_PORTDUINO
PointerQueue<meshtastic_ClientNotification> toPhoneClientNotificationQueue; PointerQueue<meshtastic_ClientNotification> toPhoneClientNotificationQueue;
#else
StaticPointerQueue<meshtastic_ClientNotification, MAX_RX_NOTIFICATION_TOPHONE> toPhoneClientNotificationQueue;
#endif
// This holds the last QueueStatus send // This holds the last QueueStatus send
meshtastic_QueueStatus lastQueueStatus; meshtastic_QueueStatus lastQueueStatus;

View File

@@ -161,6 +161,15 @@ bool NextHopRouter::stopRetransmission(NodeNum from, PacketId id)
return stopRetransmission(key); return stopRetransmission(key);
} }
bool NextHopRouter::roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *p)
{
// Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
// Return false for roles like ROUTER, REPEATER, ROUTER_LATE which should always transmit the packet at least once.
return roleAllowsCancelingDupe(p); // same logic as FloodingRouter::roleAllowsCancelingDupe
}
bool NextHopRouter::stopRetransmission(GlobalPacketId key) bool NextHopRouter::stopRetransmission(GlobalPacketId key)
{ {
auto old = findPendingPacket(key); auto old = findPendingPacket(key);
@@ -170,17 +179,21 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
to avoid canceling a transmission if it was ACKed super fast via MQTT */ to avoid canceling a transmission if it was ACKed super fast via MQTT */
if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) { if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
// We only cancel it if we are the original sender or if we're not a router(_late)/repeater // We only cancel it if we are the original sender or if we're not a router(_late)/repeater
if (isFromUs(p) || (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) {
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it // remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id); cancelSending(getFrom(p), p->id);
// now free the pooled copy for retransmission too
packetPool.release(p);
} }
} }
// Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
// get scheduled again. (This is the core of stopRetransmission.)
auto numErased = pending.erase(key); auto numErased = pending.erase(key);
assert(numErased == 1); assert(numErased == 1);
// When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
// to startRetransmission.
packetPool.release(p);
return true; return true;
} else } else
return false; return false;

View File

@@ -121,6 +121,9 @@ class NextHopRouter : public FloodingRouter
*/ */
PendingPacket *startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx = NUM_INTERMEDIATE_RETX); PendingPacket *startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx = NUM_INTERMEDIATE_RETX);
// Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
bool roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *p);
/** /**
* Stop any retransmissions we are doing of the specified node/packet ID pair * Stop any retransmissions we are doing of the specified node/packet ID pair
* *

View File

@@ -663,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.bluetooth.fixed_pin = defaultBLEPin; config.bluetooth.fixed_pin = defaultBLEPin;
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306)
bool hasScreen = true; bool hasScreen = true;
#ifdef HELTEC_MESH_NODE_T114 #ifdef HELTEC_MESH_NODE_T114
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET); uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);
@@ -673,7 +673,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#endif #endif
#elif ARCH_PORTDUINO #elif ARCH_PORTDUINO
bool hasScreen = false; bool hasScreen = false;
if (settingsMap[displayPanel]) if (portduino_config.displayPanel)
hasScreen = true; hasScreen = true;
else else
hasScreen = screen_found.port != ScanI2C::I2CPort::NO_I2C; hasScreen = screen_found.port != ScanI2C::I2CPort::NO_I2C;
@@ -1334,8 +1334,8 @@ void NodeDB::loadFromDisk()
} }
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
// set any config overrides // set any config overrides
if (settingsMap[has_configDisplayMode]) { if (portduino_config.has_configDisplayMode) {
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)settingsMap[configDisplayMode]; config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode;
} }
#endif #endif
@@ -1750,6 +1750,65 @@ void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
} }
} }
bool NodeDB::isFavorite(uint32_t nodeId)
{
// returns true if nodeId is_favorite; false if not or not found
// NODENUM_BROADCAST will never be in the DB
if (nodeId == NODENUM_BROADCAST)
return false;
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
if (lite) {
return lite->is_favorite;
}
return false;
}
bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
{
// This method is logically equivalent to:
// return isFavorite(p.from) || isFavorite(p.to);
// but is more efficient by:
// 1. doing only one pass through the database, instead of two
// 2. exiting early when a favorite is found, or if both from and to have been seen
if (p.to == NODENUM_BROADCAST)
return isFavorite(p.from); // we never store NODENUM_BROADCAST in the DB, so we only need to check p.from
meshtastic_NodeInfoLite *lite = NULL;
bool seenFrom = false;
bool seenTo = false;
for (int i = 0; i < numMeshNodes; i++) {
lite = &meshNodes->at(i);
if (lite->num == p.from) {
if (lite->is_favorite)
return true;
seenFrom = true;
}
if (lite->num == p.to) {
if (lite->is_favorite)
return true;
seenTo = true;
}
if (seenFrom && seenTo)
return false; // we've seen both, and neither is a favorite, so we can stop searching early
// Note: if we knew that sortMeshDB was always called after any change to is_favorite, we could exit early after searching
// all favorited nodes first.
}
return false;
}
void NodeDB::pause_sort(bool paused) void NodeDB::pause_sort(bool paused)
{ {
sortingIsPaused = paused; sortingIsPaused = paused;

View File

@@ -185,6 +185,16 @@ class NodeDB
*/ */
void set_favorite(bool is_favorite, uint32_t nodeId); void set_favorite(bool is_favorite, uint32_t nodeId);
/*
* Returns true if the node is in the NodeDB and marked as favorite
*/
bool isFavorite(uint32_t nodeId);
/*
* Returns true if p->from or p->to is a favorited node
*/
bool isFromOrToFavoritedNode(const meshtastic_MeshPacket &p);
/** /**
* Other functions like the node picker can request a pause in the node sorting * Other functions like the node picker can request a pause in the node sorting
*/ */

View File

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

View File

@@ -10,7 +10,7 @@
#endif #endif
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
#define RF95_MAX_POWER settingsMap[rf95_max_power] #define RF95_MAX_POWER portduino_config.rf95_max_power
#endif #endif
#ifndef RF95_MAX_POWER #ifndef RF95_MAX_POWER
#define RF95_MAX_POWER 20 #define RF95_MAX_POWER 20
@@ -94,16 +94,16 @@ void RF95Interface::setTransmitEnable(bool txon)
#ifdef RF95_TXEN #ifdef RF95_TXEN
digitalWrite(RF95_TXEN, txon ? 1 : 0); digitalWrite(RF95_TXEN, txon ? 1 : 0);
#elif ARCH_PORTDUINO #elif ARCH_PORTDUINO
if (settingsMap[txen_pin] != RADIOLIB_NC) { if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(settingsMap[txen_pin], txon ? 1 : 0); digitalWrite(portduino_config.lora_txen_pin.pin, txon ? 1 : 0);
} }
#endif #endif
#ifdef RF95_RXEN #ifdef RF95_RXEN
digitalWrite(RF95_RXEN, txon ? 0 : 1); digitalWrite(RF95_RXEN, txon ? 0 : 1);
#elif ARCH_PORTDUINO #elif ARCH_PORTDUINO
if (settingsMap[rxen_pin] != RADIOLIB_NC) { if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(settingsMap[rxen_pin], txon ? 0 : 1); digitalWrite(portduino_config.lora_rxen_pin.pin, txon ? 0 : 1);
} }
#endif #endif
} }
@@ -164,13 +164,13 @@ bool RF95Interface::init()
digitalWrite(RF95_RXEN, 1); digitalWrite(RF95_RXEN, 1);
#endif #endif
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (settingsMap[txen_pin] != RADIOLIB_NC) { if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
pinMode(settingsMap[txen_pin], OUTPUT); pinMode(portduino_config.lora_txen_pin.pin, OUTPUT);
digitalWrite(settingsMap[txen_pin], 0); digitalWrite(portduino_config.lora_txen_pin.pin, 0);
} }
if (settingsMap[rxen_pin] != RADIOLIB_NC) { if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
pinMode(settingsMap[rxen_pin], OUTPUT); pinMode(portduino_config.lora_rxen_pin.pin, OUTPUT);
digitalWrite(settingsMap[rxen_pin], 0); digitalWrite(portduino_config.lora_rxen_pin.pin, 0);
} }
#endif #endif
setTransmitEnable(false); setTransmitEnable(false);

View File

@@ -311,16 +311,33 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
return (2 * CWmax * slotTimeMsec) + pow_of_2(CWsize) * slotTimeMsec; return (2 * CWmax * slotTimeMsec) + pow_of_2(CWsize) * slotTimeMsec;
} }
/** Returns true if we should rebroadcast early like a ROUTER */
bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
{
// If we are a ROUTER or REPEATER, we always rebroadcast early
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
return true;
}
// If we are a CLIENT_BASE and the packet is from or to a favorited node, we should rebroadcast early
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
return nodeDB->isFromOrToFavoritedNode(*p);
}
return false;
}
/** The delay to use when we want to flood a message */ /** The delay to use when we want to flood a message */
uint32_t RadioInterface::getTxDelayMsecWeighted(float snr) uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
{ {
// high SNR = large CW size (Long Delay) // high SNR = large CW size (Long Delay)
// low SNR = small CW size (Short Delay) // low SNR = small CW size (Short Delay)
float snr = p->rx_snr;
uint32_t delay = 0; uint32_t delay = 0;
uint8_t CWsize = getCWsize(snr); uint8_t CWsize = getCWsize(snr);
// LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize); // LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize);
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || if (shouldRebroadcastEarlyLikeRouter(p)) {
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
delay = random(0, 2 * CWsize) * slotTimeMsec; delay = random(0, 2 * CWsize) * slotTimeMsec;
LOG_DEBUG("rx_snr found in packet. Router: setting tx delay:%d", delay); LOG_DEBUG("rx_snr found in packet. Router: setting tx delay:%d", delay);
} else { } else {

View File

@@ -180,8 +180,11 @@ class RadioInterface
/** The worst-case SNR_based packet delay */ /** The worst-case SNR_based packet delay */
uint32_t getTxDelayMsecWeightedWorst(float snr); uint32_t getTxDelayMsecWeightedWorst(float snr);
/** Returns true if we should rebroadcast early like a ROUTER */
bool shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p);
/** The delay to use when we want to flood a message. Use a weighted scale based on SNR */ /** The delay to use when we want to flood a message. Use a weighted scale based on SNR */
uint32_t getTxDelayMsecWeighted(float snr); uint32_t getTxDelayMsecWeighted(meshtastic_MeshPacket *p);
/** If the packet is not already in the late rebroadcast window, move it there */ /** If the packet is not already in the late rebroadcast window, move it there */
virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; } virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }

View File

@@ -310,7 +310,7 @@ void RadioLibInterface::setTransmitDelay()
// So we want to make sure the other side has had a chance to reconfigure its radio. // So we want to make sure the other side has had a chance to reconfigure its radio.
if (p->tx_after) { if (p->tx_after) {
unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p->rx_snr) : getTxDelayMsec(); unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p) : getTxDelayMsec();
unsigned long now = millis(); unsigned long now = millis();
p->tx_after = min(max(p->tx_after + add_delay, now + add_delay), now + 2 * getTxDelayMsecWeightedWorst(p->rx_snr)); p->tx_after = min(max(p->tx_after + add_delay, now + add_delay), now + 2 * getTxDelayMsecWeightedWorst(p->rx_snr));
notifyLater(p->tx_after - now, TRANSMIT_DELAY_COMPLETED, false); notifyLater(p->tx_after - now, TRANSMIT_DELAY_COMPLETED, false);
@@ -323,7 +323,7 @@ void RadioLibInterface::setTransmitDelay()
} else { } else {
// If there is a SNR, start a timer scaled based on that SNR. // If there is a SNR, start a timer scaled based on that SNR.
LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr); LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
startTransmitTimerSNR(p->rx_snr); startTransmitTimerRebroadcast(p);
} }
} }
@@ -336,11 +336,11 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
} }
} }
void RadioLibInterface::startTransmitTimerSNR(float snr) void RadioLibInterface::startTransmitTimerRebroadcast(meshtastic_MeshPacket *p)
{ {
// If we have work to do and the timer wasn't already scheduled, schedule it now // If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.empty()) { if (!txQueue.empty()) {
uint32_t delay = getTxDelayMsecWeighted(snr); uint32_t delay = getTxDelayMsecWeighted(p);
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
} }
} }
@@ -417,7 +417,7 @@ void RadioLibInterface::handleReceiveInterrupt()
int state = iface->readData((uint8_t *)&radioBuffer, length); int state = iface->readData((uint8_t *)&radioBuffer, length);
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (settingsMap[logoutputlevel] == level_trace) { if (portduino_config.logoutputlevel == level_trace) {
printBytes("Raw incoming packet: ", (uint8_t *)&radioBuffer, length); printBytes("Raw incoming packet: ", (uint8_t *)&radioBuffer, length);
} }
#endif #endif

View File

@@ -161,7 +161,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
* timer scaled to SNR of to be flooded packet * timer scaled to SNR of to be flooded packet
* @return Timestamp after which the packet may be sent * @return Timestamp after which the packet may be sent
*/ */
void startTransmitTimerSNR(float snr); void startTransmitTimerRebroadcast(meshtastic_MeshPacket *p);
void handleTransmitInterrupt(); void handleTransmitInterrupt();
void handleReceiveInterrupt(); void handleReceiveInterrupt();

View File

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

View File

@@ -5,6 +5,7 @@
#include "MeshService.h" #include "MeshService.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "RTC.h" #include "RTC.h"
#include "configuration.h" #include "configuration.h"
#include "detect/LoRaRadioType.h" #include "detect/LoRaRadioType.h"
#include "main.h" #include "main.h"
@@ -27,14 +28,24 @@
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX // I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
// And every TX packet might have a retransmission packet or an ack alive at any moment // And every TX packet might have a retransmission packet or an ack alive at any moment
#ifdef ARCH_PORTDUINO
// Portduino (native) targets can use dynamic memory pools with runtime-configurable sizes
#define MAX_PACKETS \ #define MAX_PACKETS \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending) 2) // max number of packets which can be in flight (either queued from reception or queued for sending)
// static MemoryPool<MeshPacket> staticPool(MAX_PACKETS); static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
static MemoryDynamic<meshtastic_MeshPacket> staticPool; Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
#else
// Embedded targets use static memory pools with compile-time constants
#define MAX_PACKETS_STATIC \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
static MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
Allocator<meshtastic_MeshPacket> &packetPool = staticPool; Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
#endif
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__)); static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
@@ -275,7 +286,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
// If the packet is not yet encrypted, do so now // If the packet is not yet encrypted, do so now
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p); meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("Router::send", p_decoded);
auto encodeResult = perhapsEncode(p); auto encodeResult = perhapsEncode(p);
if (encodeResult != meshtastic_Routing_Error_NONE) { if (encodeResult != meshtastic_Routing_Error_NONE) {
@@ -416,6 +430,36 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
} }
} }
} }
#if HAS_UDP_MULTICAST
// Fallback: for UDP multicast, try default preset names with default PSK if normal channel match failed
if (!decrypted && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP) {
if (channels.setDefaultPresetCryptoForHash(p->channel)) {
memcpy(bytes, p->encrypted.bytes, rawSize);
crypto->decrypt(p->from, p->id, rawSize, bytes);
meshtastic_Data decodedtmp;
memset(&decodedtmp, 0, sizeof(decodedtmp));
if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) &&
decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) {
p->decoded = decodedtmp;
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
// Map to our local default channel index (name+PSK default), not necessarily primary
ChannelIndex defaultIndex = channels.getPrimaryIndex();
for (ChannelIndex i = 0; i < channels.getNumChannels(); ++i) {
if (channels.isDefaultChannel(i)) {
defaultIndex = i;
break;
}
}
chIndex = defaultIndex;
decrypted = true;
} else {
LOG_WARN("UDP fallback decode attempted but failed for hash 0x%x", p->channel);
}
}
}
#endif
if (decrypted) { if (decrypted) {
// parsing was successful // parsing was successful
p->channel = chIndex; // change to store the index instead of the hash p->channel = chIndex; // change to store the index instead of the hash
@@ -446,7 +490,7 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
#if ENABLE_JSON_LOGGING #if ENABLE_JSON_LOGGING
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str()); LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
#elif ARCH_PORTDUINO #elif ARCH_PORTDUINO
if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) { if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str()); LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
} }
#endif #endif
@@ -607,8 +651,11 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
bool skipHandle = false; bool skipHandle = false;
// Also, we should set the time from the ISR and it should have msec level resolution // Also, we should set the time from the ISR and it should have msec level resolution
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
// Store a copy of encrypted packet for MQTT // Store a copy of encrypted packet for MQTT
DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p); meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted);
// Take those raw bytes and convert them back into a well structured protobuf we can understand // Take those raw bytes and convert them back into a well structured protobuf we can understand
auto decodedState = perhapsDecode(p); auto decodedState = perhapsDecode(p);
@@ -656,7 +703,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
// call modules here // call modules here
// If this could be a spoofed packet, don't let the modules see it. // If this could be a spoofed packet, don't let the modules see it.
if (!skipHandle && p->from != nodeDB->getNodeNum()) { if (!skipHandle) {
MeshModule::callModules(*p, src); MeshModule::callModules(*p, src);
#if !MESHTASTIC_EXCLUDE_MQTT #if !MESHTASTIC_EXCLUDE_MQTT
@@ -670,8 +717,6 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
!isFromUs(p) && mqtt) !isFromUs(p) && mqtt)
mqtt->onSend(*p_encrypted, *p, p->channel); mqtt->onSend(*p_encrypted, *p, p->channel);
#endif #endif
} else if (p->from == nodeDB->getNodeNum() && !skipHandle) {
MeshModule::callModules(*p, src);
} }
packetPool.release(p_encrypted); // Release the encrypted packet packetPool.release(p_encrypted); // Release the encrypted packet
@@ -685,7 +730,7 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str()); LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str());
#elif ARCH_PORTDUINO #elif ARCH_PORTDUINO
// Even ignored packets get logged in the trace // Even ignored packets get logged in the trace
if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) { if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str()); LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str());
} }

View File

@@ -12,7 +12,7 @@
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not // Particular boards might define a different max power based on what their hardware can do, default to max power output if not
// specified (may be dangerous if using external PA and SX126x power config forgotten) // specified (may be dangerous if using external PA and SX126x power config forgotten)
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
#define SX126X_MAX_POWER settingsMap[sx126x_max_power] #define SX126X_MAX_POWER portduino_config.sx126x_max_power
#endif #endif
#ifndef SX126X_MAX_POWER #ifndef SX126X_MAX_POWER
#define SX126X_MAX_POWER 22 #define SX126X_MAX_POWER 22
@@ -53,10 +53,10 @@ template <typename T> bool SX126xInterface<T>::init()
#endif #endif
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000; tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
if (settingsMap[sx126x_ant_sw_pin] != RADIOLIB_NC) { if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) {
digitalWrite(settingsMap[sx126x_ant_sw_pin], HIGH); digitalWrite(portduino_config.lora_sx126x_ant_sw_pin.pin, HIGH);
pinMode(settingsMap[sx126x_ant_sw_pin], OUTPUT); pinMode(portduino_config.lora_sx126x_ant_sw_pin.pin, OUTPUT);
} }
#endif #endif
if (tcxoVoltage == 0.0) if (tcxoVoltage == 0.0)
@@ -98,7 +98,7 @@ template <typename T> bool SX126xInterface<T>::init()
bool dio2AsRfSwitch = true; bool dio2AsRfSwitch = true;
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
bool dio2AsRfSwitch = false; bool dio2AsRfSwitch = false;
if (settingsMap[dio2_as_rf_switch]) { if (portduino_config.dio2_as_rf_switch) {
dio2AsRfSwitch = true; dio2AsRfSwitch = true;
} }
#else #else
@@ -112,9 +112,9 @@ template <typename T> bool SX126xInterface<T>::init()
// no effect // no effect
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (res == RADIOLIB_ERR_NONE) { if (res == RADIOLIB_ERR_NONE) {
LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", settingsMap[rxen_pin], LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", portduino_config.lora_rxen_pin.pin,
settingsMap[txen_pin]); portduino_config.lora_txen_pin.pin);
lora.setRfSwitchPins(settingsMap[rxen_pin], settingsMap[txen_pin]); lora.setRfSwitchPins(portduino_config.lora_rxen_pin.pin, portduino_config.lora_txen_pin.pin);
} }
#else #else
#ifndef SX126X_RXEN #ifndef SX126X_RXEN

View File

@@ -11,7 +11,7 @@
// Particular boards might define a different max power based on what their hardware can do // Particular boards might define a different max power based on what their hardware can do
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
#define SX128X_MAX_POWER settingsMap[sx128x_max_power] #define SX128X_MAX_POWER portduino_config.sx128x_max_power
#endif #endif
#ifndef SX128X_MAX_POWER #ifndef SX128X_MAX_POWER
#define SX128X_MAX_POWER 13 #define SX128X_MAX_POWER 13
@@ -41,13 +41,13 @@ template <typename T> bool SX128xInterface<T>::init()
#endif #endif
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (settingsMap[rxen_pin] != RADIOLIB_NC) { if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
pinMode(settingsMap[rxen_pin], OUTPUT); pinMode(portduino_config.lora_rxen_pin.pin, OUTPUT);
digitalWrite(settingsMap[rxen_pin], LOW); // Set low before becoming an output digitalWrite(portduino_config.lora_rxen_pin.pin, LOW); // Set low before becoming an output
} }
if (settingsMap[txen_pin] != RADIOLIB_NC) { if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
pinMode(settingsMap[txen_pin], OUTPUT); pinMode(portduino_config.lora_txen_pin.pin, OUTPUT);
digitalWrite(settingsMap[txen_pin], LOW); // Set low before becoming an output digitalWrite(portduino_config.lora_txen_pin.pin, LOW); // Set low before becoming an output
} }
#else #else
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // set not rx or tx mode #if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // set not rx or tx mode
@@ -93,8 +93,9 @@ template <typename T> bool SX128xInterface<T>::init()
lora.setRfSwitchPins(SX128X_RXEN, SX128X_TXEN); lora.setRfSwitchPins(SX128X_RXEN, SX128X_TXEN);
} }
#elif ARCH_PORTDUINO #elif ARCH_PORTDUINO
if (res == RADIOLIB_ERR_NONE && settingsMap[rxen_pin] != RADIOLIB_NC && settingsMap[txen_pin] != RADIOLIB_NC) { if (res == RADIOLIB_ERR_NONE && portduino_config.lora_rxen_pin.pin != RADIOLIB_NC &&
lora.setRfSwitchPins(settingsMap[rxen_pin], settingsMap[txen_pin]); portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
lora.setRfSwitchPins(portduino_config.lora_rxen_pin.pin, portduino_config.lora_txen_pin.pin);
} }
#endif #endif
@@ -174,11 +175,11 @@ template <typename T> void SX128xInterface<T>::setStandby()
LOG_ERROR("SX128x standby %s%d", radioLibErr, err); LOG_ERROR("SX128x standby %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE); assert(err == RADIOLIB_ERR_NONE);
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (settingsMap[rxen_pin] != RADIOLIB_NC) { if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(settingsMap[rxen_pin], LOW); digitalWrite(portduino_config.lora_rxen_pin.pin, LOW);
} }
if (settingsMap[txen_pin] != RADIOLIB_NC) { if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(settingsMap[txen_pin], LOW); digitalWrite(portduino_config.lora_txen_pin.pin, LOW);
} }
#else #else
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn off RX and TX power #if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn off RX and TX power
@@ -210,11 +211,11 @@ template <typename T> void SX128xInterface<T>::addReceiveMetadata(meshtastic_Mes
template <typename T> void SX128xInterface<T>::configHardwareForSend() template <typename T> void SX128xInterface<T>::configHardwareForSend()
{ {
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (settingsMap[txen_pin] != RADIOLIB_NC) { if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(settingsMap[txen_pin], HIGH); digitalWrite(portduino_config.lora_txen_pin.pin, HIGH);
} }
if (settingsMap[rxen_pin] != RADIOLIB_NC) { if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(settingsMap[rxen_pin], LOW); digitalWrite(portduino_config.lora_rxen_pin.pin, LOW);
} }
#else #else
@@ -241,11 +242,11 @@ template <typename T> void SX128xInterface<T>::startReceive()
setStandby(); setStandby();
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (settingsMap[rxen_pin] != RADIOLIB_NC) { if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(settingsMap[rxen_pin], HIGH); digitalWrite(portduino_config.lora_rxen_pin.pin, HIGH);
} }
if (settingsMap[txen_pin] != RADIOLIB_NC) { if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(settingsMap[txen_pin], LOW); digitalWrite(portduino_config.lora_txen_pin.pin, LOW);
} }
#else #else

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

@@ -173,28 +173,10 @@ typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags {
meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1 meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1
} meshtastic_Config_NetworkConfig_ProtocolFlags; } meshtastic_Config_NetworkConfig_ProtocolFlags;
/* How the GPS coordinates are displayed on the OLED screen. */ /* Deprecated in 2.7.4: Unused */
typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat { typedef enum _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat {
/* GPS coordinates are displayed in the normal decimal degrees format: meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED = 0
DD.DDDDDD DDD.DDDDDD */ } meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat;
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC = 0,
/* GPS coordinates are displayed in the degrees minutes seconds format:
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS = 1,
/* Universal Transverse Mercator format:
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM = 2,
/* Military Grid Reference System format:
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
E is easting, N is northing */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS = 3,
/* Open Location Code (aka Plus Codes). */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC = 4,
/* Ordnance Survey Grid Reference (the National Grid System of the UK).
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
E is the easting, N is the northing */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR = 5
} meshtastic_Config_DisplayConfig_GpsCoordinateFormat;
/* Unit display preference */ /* Unit display preference */
typedef enum _meshtastic_Config_DisplayConfig_DisplayUnits { typedef enum _meshtastic_Config_DisplayConfig_DisplayUnits {
@@ -491,7 +473,7 @@ typedef struct _meshtastic_Config_DisplayConfig {
uint32_t screen_on_secs; uint32_t screen_on_secs;
/* Deprecated in 2.7.4: Unused /* Deprecated in 2.7.4: Unused
How the GPS coordinates are formatted on the OLED screen. */ How the GPS coordinates are formatted on the OLED screen. */
meshtastic_Config_DisplayConfig_GpsCoordinateFormat gps_format; meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat gps_format;
/* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds. /* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds.
Potentially useful for devices without user buttons. */ Potentially useful for devices without user buttons. */
uint32_t auto_screen_carousel_secs; uint32_t auto_screen_carousel_secs;
@@ -515,6 +497,9 @@ typedef struct _meshtastic_Config_DisplayConfig {
/* If false (default), the device will display the time in 24-hour format on screen. /* If false (default), the device will display the time in 24-hour format on screen.
If true, the device will display the time in 12-hour format on screen. */ If true, the device will display the time in 12-hour format on screen. */
bool use_12h_clock; bool use_12h_clock;
/* If false (default), the device will use short names for various display screens.
If true, node names will show in long format */
bool use_long_node_name;
} meshtastic_Config_DisplayConfig; } meshtastic_Config_DisplayConfig;
/* Lora Config */ /* Lora Config */
@@ -678,9 +663,9 @@ extern "C" {
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST #define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1)) #define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1))
#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC #define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR #define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1)) #define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat)(meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED+1))
#define _meshtastic_Config_DisplayConfig_DisplayUnits_MIN meshtastic_Config_DisplayConfig_DisplayUnits_METRIC #define _meshtastic_Config_DisplayConfig_DisplayUnits_MIN meshtastic_Config_DisplayConfig_DisplayUnits_METRIC
#define _meshtastic_Config_DisplayConfig_DisplayUnits_MAX meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL #define _meshtastic_Config_DisplayConfig_DisplayUnits_MAX meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL
@@ -721,7 +706,7 @@ extern "C" {
#define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode #define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode
#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_GpsCoordinateFormat #define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat
#define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits #define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits
#define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType #define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType
#define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode #define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode
@@ -742,7 +727,7 @@ extern "C" {
#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0}
#define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -753,7 +738,7 @@ extern "C" {
#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0}
#define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0} #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -820,6 +805,7 @@ extern "C" {
#define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10 #define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
#define meshtastic_Config_DisplayConfig_compass_orientation_tag 11 #define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
#define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12 #define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
#define meshtastic_Config_DisplayConfig_use_long_node_name_tag 13
#define meshtastic_Config_LoRaConfig_use_preset_tag 1 #define meshtastic_Config_LoRaConfig_use_preset_tag 1
#define meshtastic_Config_LoRaConfig_modem_preset_tag 2 #define meshtastic_Config_LoRaConfig_modem_preset_tag 2
#define meshtastic_Config_LoRaConfig_bandwidth_tag 3 #define meshtastic_Config_LoRaConfig_bandwidth_tag 3
@@ -965,7 +951,8 @@ X(a, STATIC, SINGULAR, UENUM, displaymode, 8) \
X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \ X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \
X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \ X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \
X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11) \ X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11) \
X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12) X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12) \
X(a, STATIC, SINGULAR, BOOL, use_long_node_name, 13)
#define meshtastic_Config_DisplayConfig_CALLBACK NULL #define meshtastic_Config_DisplayConfig_CALLBACK NULL
#define meshtastic_Config_DisplayConfig_DEFAULT NULL #define meshtastic_Config_DisplayConfig_DEFAULT NULL
@@ -1043,7 +1030,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
#define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
#define meshtastic_Config_BluetoothConfig_size 10 #define meshtastic_Config_BluetoothConfig_size 10
#define meshtastic_Config_DeviceConfig_size 100 #define meshtastic_Config_DeviceConfig_size 100
#define meshtastic_Config_DisplayConfig_size 32 #define meshtastic_Config_DisplayConfig_size 34
#define meshtastic_Config_LoRaConfig_size 85 #define meshtastic_Config_LoRaConfig_size 85
#define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20
#define meshtastic_Config_NetworkConfig_size 204 #define meshtastic_Config_NetworkConfig_size 204

View File

@@ -28,3 +28,5 @@ PB_BIND(meshtastic_Map, meshtastic_Map, AUTO)

View File

@@ -66,12 +66,40 @@ typedef enum _meshtastic_Language {
meshtastic_Language_UKRAINIAN = 16, meshtastic_Language_UKRAINIAN = 16,
/* Bulgarian */ /* Bulgarian */
meshtastic_Language_BULGARIAN = 17, meshtastic_Language_BULGARIAN = 17,
/* Czech */
meshtastic_Language_CZECH = 18,
/* Simplified Chinese (experimental) */ /* Simplified Chinese (experimental) */
meshtastic_Language_SIMPLIFIED_CHINESE = 30, meshtastic_Language_SIMPLIFIED_CHINESE = 30,
/* Traditional Chinese (experimental) */ /* Traditional Chinese (experimental) */
meshtastic_Language_TRADITIONAL_CHINESE = 31 meshtastic_Language_TRADITIONAL_CHINESE = 31
} meshtastic_Language; } meshtastic_Language;
/* How the GPS coordinates are displayed on the OLED screen. */
typedef enum _meshtastic_DeviceUIConfig_GpsCoordinateFormat {
/* GPS coordinates are displayed in the normal decimal degrees format:
DD.DDDDDD DDD.DDDDDD */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC = 0,
/* GPS coordinates are displayed in the degrees minutes seconds format:
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS = 1,
/* Universal Transverse Mercator format:
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM = 2,
/* Military Grid Reference System format:
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
E is easting, N is northing */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS = 3,
/* Open Location Code (aka Plus Codes). */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC = 4,
/* Ordnance Survey Grid Reference (the National Grid System of the UK).
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
E is the easting, N is the northing */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR = 5,
/* Maidenhead Locator System
Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System */
meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS = 6
} meshtastic_DeviceUIConfig_GpsCoordinateFormat;
/* Struct definitions */ /* Struct definitions */
typedef struct _meshtastic_NodeFilter { typedef struct _meshtastic_NodeFilter {
/* Filter unknown nodes */ /* Filter unknown nodes */
@@ -161,6 +189,8 @@ typedef struct _meshtastic_DeviceUIConfig {
/* Clockface analog style /* Clockface analog style
true for analog clockface, false for digital clockface */ true for analog clockface, false for digital clockface */
bool is_clockface_analog; bool is_clockface_analog;
/* How the GPS coordinates are formatted on the OLED screen. */
meshtastic_DeviceUIConfig_GpsCoordinateFormat gps_format;
} meshtastic_DeviceUIConfig; } meshtastic_DeviceUIConfig;
@@ -181,9 +211,14 @@ extern "C" {
#define _meshtastic_Language_MAX meshtastic_Language_TRADITIONAL_CHINESE #define _meshtastic_Language_MAX meshtastic_Language_TRADITIONAL_CHINESE
#define _meshtastic_Language_ARRAYSIZE ((meshtastic_Language)(meshtastic_Language_TRADITIONAL_CHINESE+1)) #define _meshtastic_Language_ARRAYSIZE ((meshtastic_Language)(meshtastic_Language_TRADITIONAL_CHINESE+1))
#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC
#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MAX meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS
#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_DeviceUIConfig_GpsCoordinateFormat)(meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS+1))
#define meshtastic_DeviceUIConfig_theme_ENUMTYPE meshtastic_Theme #define meshtastic_DeviceUIConfig_theme_ENUMTYPE meshtastic_Theme
#define meshtastic_DeviceUIConfig_language_ENUMTYPE meshtastic_Language #define meshtastic_DeviceUIConfig_language_ENUMTYPE meshtastic_Language
#define meshtastic_DeviceUIConfig_compass_mode_ENUMTYPE meshtastic_CompassMode #define meshtastic_DeviceUIConfig_compass_mode_ENUMTYPE meshtastic_CompassMode
#define meshtastic_DeviceUIConfig_gps_format_ENUMTYPE meshtastic_DeviceUIConfig_GpsCoordinateFormat
@@ -191,12 +226,12 @@ extern "C" {
/* Initializer values for message structs */ /* Initializer values for message structs */
#define meshtastic_DeviceUIConfig_init_default {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0} #define meshtastic_DeviceUIConfig_init_default {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
#define meshtastic_NodeFilter_init_default {0, 0, 0, 0, 0, "", 0} #define meshtastic_NodeFilter_init_default {0, 0, 0, 0, 0, "", 0}
#define meshtastic_NodeHighlight_init_default {0, 0, 0, 0, ""} #define meshtastic_NodeHighlight_init_default {0, 0, 0, 0, ""}
#define meshtastic_GeoPoint_init_default {0, 0, 0} #define meshtastic_GeoPoint_init_default {0, 0, 0}
#define meshtastic_Map_init_default {false, meshtastic_GeoPoint_init_default, "", 0} #define meshtastic_Map_init_default {false, meshtastic_GeoPoint_init_default, "", 0}
#define meshtastic_DeviceUIConfig_init_zero {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0} #define meshtastic_DeviceUIConfig_init_zero {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
#define meshtastic_NodeFilter_init_zero {0, 0, 0, 0, 0, "", 0} #define meshtastic_NodeFilter_init_zero {0, 0, 0, 0, 0, "", 0}
#define meshtastic_NodeHighlight_init_zero {0, 0, 0, 0, ""} #define meshtastic_NodeHighlight_init_zero {0, 0, 0, 0, ""}
#define meshtastic_GeoPoint_init_zero {0, 0, 0} #define meshtastic_GeoPoint_init_zero {0, 0, 0}
@@ -239,6 +274,7 @@ extern "C" {
#define meshtastic_DeviceUIConfig_compass_mode_tag 16 #define meshtastic_DeviceUIConfig_compass_mode_tag 16
#define meshtastic_DeviceUIConfig_screen_rgb_color_tag 17 #define meshtastic_DeviceUIConfig_screen_rgb_color_tag 17
#define meshtastic_DeviceUIConfig_is_clockface_analog_tag 18 #define meshtastic_DeviceUIConfig_is_clockface_analog_tag 18
#define meshtastic_DeviceUIConfig_gps_format_tag 19
/* Struct field encoding specification for nanopb */ /* Struct field encoding specification for nanopb */
#define meshtastic_DeviceUIConfig_FIELDLIST(X, a) \ #define meshtastic_DeviceUIConfig_FIELDLIST(X, a) \
@@ -259,7 +295,8 @@ X(a, STATIC, SINGULAR, BYTES, calibration_data, 14) \
X(a, STATIC, OPTIONAL, MESSAGE, map_data, 15) \ X(a, STATIC, OPTIONAL, MESSAGE, map_data, 15) \
X(a, STATIC, SINGULAR, UENUM, compass_mode, 16) \ X(a, STATIC, SINGULAR, UENUM, compass_mode, 16) \
X(a, STATIC, SINGULAR, UINT32, screen_rgb_color, 17) \ X(a, STATIC, SINGULAR, UINT32, screen_rgb_color, 17) \
X(a, STATIC, SINGULAR, BOOL, is_clockface_analog, 18) X(a, STATIC, SINGULAR, BOOL, is_clockface_analog, 18) \
X(a, STATIC, SINGULAR, UENUM, gps_format, 19)
#define meshtastic_DeviceUIConfig_CALLBACK NULL #define meshtastic_DeviceUIConfig_CALLBACK NULL
#define meshtastic_DeviceUIConfig_DEFAULT NULL #define meshtastic_DeviceUIConfig_DEFAULT NULL
#define meshtastic_DeviceUIConfig_node_filter_MSGTYPE meshtastic_NodeFilter #define meshtastic_DeviceUIConfig_node_filter_MSGTYPE meshtastic_NodeFilter
@@ -316,7 +353,7 @@ extern const pb_msgdesc_t meshtastic_Map_msg;
/* Maximum encoded size of messages (where known) */ /* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size #define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size
#define meshtastic_DeviceUIConfig_size 201 #define meshtastic_DeviceUIConfig_size 204
#define meshtastic_GeoPoint_size 33 #define meshtastic_GeoPoint_size 33
#define meshtastic_Map_size 58 #define meshtastic_Map_size 58
#define meshtastic_NodeFilter_size 47 #define meshtastic_NodeFilter_size 47

View File

@@ -360,7 +360,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
/* Maximum encoded size of messages (where known) */ /* Maximum encoded size of messages (where known) */
/* meshtastic_NodeDatabase_size depends on runtime parameters */ /* meshtastic_NodeDatabase_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
#define meshtastic_BackupPreferences_size 2273 #define meshtastic_BackupPreferences_size 2277
#define meshtastic_ChannelFile_size 718 #define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1737 #define meshtastic_DeviceState_size 1737
#define meshtastic_NodeInfoLite_size 196 #define meshtastic_NodeInfoLite_size 196

View File

@@ -187,8 +187,8 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
/* Maximum encoded size of messages (where known) */ /* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
#define meshtastic_LocalConfig_size 747 #define meshtastic_LocalConfig_size 749
#define meshtastic_LocalModuleConfig_size 671 #define meshtastic_LocalModuleConfig_size 673
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */

View File

@@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_T_DECK_PRO = 102, meshtastic_HardwareModel_T_DECK_PRO = 102,
/* Lilygo TLora Pager */ /* Lilygo TLora Pager */
meshtastic_HardwareModel_T_LORA_PAGER = 103, meshtastic_HardwareModel_T_LORA_PAGER = 103,
/* GAT562 Mesh Trial Tracker */ /* M5Stack Reserved */
meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104, meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
/* RAKwireless WisMesh Tag */ /* RAKwireless WisMesh Tag */
meshtastic_HardwareModel_WISMESH_TAG = 105, meshtastic_HardwareModel_WISMESH_TAG = 105,
/* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */ /* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -274,6 +274,10 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_T_ECHO_LITE = 109, meshtastic_HardwareModel_T_ECHO_LITE = 109,
/* New Heltec LoRA32 with ESP32-S3 CPU */ /* New Heltec LoRA32 with ESP32-S3 CPU */
meshtastic_HardwareModel_HELTEC_V4 = 110, meshtastic_HardwareModel_HELTEC_V4 = 110,
/* M5Stack C6L */
meshtastic_HardwareModel_M5STACK_C6L = 111,
/* M5Stack Cardputer Adv */
meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
/* ------------------------------------------------------------------------------------------------------------------------------------------ /* ------------------------------------------------------------------------------------------------------------------------------------------
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. 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

@@ -356,6 +356,9 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig {
uint32_t health_update_interval; uint32_t health_update_interval;
/* Enable/Disable the health telemetry module on-device display */ /* Enable/Disable the health telemetry module on-device display */
bool health_screen_enabled; bool health_screen_enabled;
/* Enable/Disable the device telemetry module to send metrics to the mesh
Note: We will still send telemtry to the connected phone / client every minute over the API */
bool device_telemetry_enabled;
} meshtastic_ModuleConfig_TelemetryConfig; } meshtastic_ModuleConfig_TelemetryConfig;
/* Canned Messages Module Config */ /* Canned Messages Module Config */
@@ -523,7 +526,7 @@ extern "C" {
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0, 0}
#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
#define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -539,7 +542,7 @@ extern "C" {
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0, 0}
#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0}
#define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -627,6 +630,7 @@ extern "C" {
#define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11 #define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11
#define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12 #define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12
#define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13 #define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13
#define meshtastic_ModuleConfig_TelemetryConfig_device_telemetry_enabled_tag 14
#define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1 #define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1
#define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2
#define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3
@@ -825,7 +829,8 @@ X(a, STATIC, SINGULAR, UINT32, power_update_interval, 9) \
X(a, STATIC, SINGULAR, BOOL, power_screen_enabled, 10) \ X(a, STATIC, SINGULAR, BOOL, power_screen_enabled, 10) \
X(a, STATIC, SINGULAR, BOOL, health_measurement_enabled, 11) \ X(a, STATIC, SINGULAR, BOOL, health_measurement_enabled, 11) \
X(a, STATIC, SINGULAR, UINT32, health_update_interval, 12) \ X(a, STATIC, SINGULAR, UINT32, health_update_interval, 12) \
X(a, STATIC, SINGULAR, BOOL, health_screen_enabled, 13) X(a, STATIC, SINGULAR, BOOL, health_screen_enabled, 13) \
X(a, STATIC, SINGULAR, BOOL, device_telemetry_enabled, 14)
#define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL #define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL #define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL
@@ -910,7 +915,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
#define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_SerialConfig_size 28
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24
#define meshtastic_ModuleConfig_TelemetryConfig_size 46 #define meshtastic_ModuleConfig_TelemetryConfig_size 48
#define meshtastic_ModuleConfig_size 227 #define meshtastic_ModuleConfig_size 227
#define meshtastic_RemoteHardwarePin_size 21 #define meshtastic_RemoteHardwarePin_size 21

View File

@@ -292,11 +292,14 @@ JSONArray htmlListDir(const char *dirname, uint8_t levels)
JSONObject thisFileMap; JSONObject thisFileMap;
thisFileMap["size"] = new JSONValue((int)file.size()); thisFileMap["size"] = new JSONValue((int)file.size());
#ifdef ARCH_ESP32 #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 #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 #endif
if (String(file.name()).substring(1).endsWith(".gz")) { String tempName = String(file.name()).substring(1);
if (tempName.endsWith(".gz")) {
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
String modifiedFile = String(file.path()).substring(1); String modifiedFile = String(file.path()).substring(1);
#else #else
@@ -339,7 +342,8 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
JSONValue *value = new JSONValue(jsonObjOuter); JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str()); std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value; delete value;
@@ -367,7 +371,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter; JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok"); jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter); JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str()); std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value; delete value;
return; return;
} else { } else {
@@ -376,7 +381,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter; JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("Error"); jsonObjOuter["status"] = new JSONValue("Error");
JSONValue *value = new JSONValue(jsonObjOuter); JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str()); std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value; delete value;
return; return;
} }
@@ -622,10 +628,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
tempArray.push_back(new JSONValue((int)logArray[i])); tempArray.push_back(new JSONValue((int)logArray[i]));
} }
JSONValue *result = new JSONValue(tempArray); JSONValue *result = new JSONValue(tempArray);
// Clean up original array to prevent memory leak // Note: Don't delete tempArray elements here - JSONValue now owns them
for (auto *val : tempArray) {
delete val;
}
return result; return result;
}; };
@@ -656,7 +659,9 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
// data->wifi // data->wifi
JSONObject jsonObjWifi; JSONObject jsonObjWifi;
jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI()); 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 // data->memory
JSONObject jsonObjMemory; JSONObject jsonObjMemory;
@@ -702,7 +707,8 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok"); jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream // serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter); JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str()); std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value; delete value;
} }
@@ -773,7 +779,8 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok"); jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream // serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter); JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str()); std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value; delete value;
// Clean up the nodesArray to prevent memory leak // Clean up the nodesArray to prevent memory leak
@@ -926,7 +933,8 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter; JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok"); jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter); JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str()); std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value; delete value;
} }
@@ -968,7 +976,8 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
// serialize and write it to the stream // serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter); JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str()); std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value; delete value;
// Clean up the networkObjs to prevent memory leak // 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 // 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])) // RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
#ifndef MAX_RX_TOPHONE #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 #define MAX_RX_TOPHONE 32
#endif #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 /// Verify baseline assumption of node size. If it increases, we need to reevaluate
/// the impact of its memory footprint, notably on MAX_NUM_NODES. /// the impact of its memory footprint, notably on MAX_NUM_NODES.

Some files were not shown because too many files have changed in this diff Show More