Compare commits

...

186 Commits

Author SHA1 Message Date
Jonathan Bennett
4243d258a4 trunk 2025-08-17 15:45:42 -05:00
Jonathan Bennett
6b5ee2f10a Loopback support in portduino with crc check to ignore own packets 2025-08-17 15:34:04 -05:00
Manuel
78c5309e9a apply 180 degree hw roration Indicator BaseUI (#7660) 2025-08-17 14:48:24 -05:00
Thomas Göttgens
9feb1d378e Support for T-Echo Lite, credits to @Szetya for doing all the heavy lifting! (#7636)
* Support for T-Echo Lite, credts to @Szetya for doing all the heavy lifting!
* move define to ini file
2025-08-17 13:37:12 +02:00
Jonathan Bennett
e5e8683cdb Don't update the NodeDB if the nodeinfo has a mismatching public key (#7652) 2025-08-17 05:56:06 -05:00
Jonathan Bennett
d538ad170c Add onboard message for devices with screens (#7655)
* Add onboard message for devices with screens

* Add message for TFT
2025-08-17 05:55:00 -05:00
Jonathan Bennett
c64c196778 Wait for lead up before enable longlong action (#7648) 2025-08-16 06:10:44 -05:00
github-actions[bot]
8e552a9f0c Upgrade trunk (#7626)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-08-16 05:57:20 -05:00
Ben Meadors
a02017a5c8 Remove JSON serialization from most NRF52 targets (#7640)
* Remove JSON serialization from most NRF52 targets

* Slin networking base down for NRF52 by removing syslog

* Update platformio.ini
2025-08-15 19:45:41 -05:00
github-actions[bot]
0046d957f1 Update protobufs (#7647)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-08-15 21:42:51 +02:00
Jonathan Bennett
4a241deb96 Thinknode button and backlight fixes (#7641)
* Thinknode button and backlight fixes

* Save backlight value between reboots
2025-08-15 14:41:21 -05:00
GUVWAF
8d5ae1d5d2 Fix marking LoRa transport mechanism (#7634) 2025-08-15 19:09:25 +02:00
Ben Meadors
e1e89a5e62 Don't include OLED fonts for international character sets by default (#7639) 2025-08-15 09:03:21 -05:00
Ben Meadors
a7be93449e Spacing 2025-08-15 09:00:09 -05:00
Ben Meadors
c8694f9f2d Fix Tracerouter warnings (#7637)
* Static cast to avoid signed comparison

* Another one
2025-08-15 07:03:14 -05:00
Austin
062168cd42 Docker: Update Debian images to trixie (#7621) 2025-08-15 06:19:49 -05:00
Ford Jones
1877a2c531 Prompt user to select destination upon launch of canned message module (#7624)
Co-authored-by: Jason P <applewiz@mac.com>
2025-08-15 05:31:11 -05:00
Austin
52f0e5a3db Fix 'buildroot' target (OpenWRT) (#7620) 2025-08-14 12:31:25 -05:00
github-actions[bot]
ac8c372349 Upgrade trunk to 1.25.0 (#7432)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-08-13 06:26:08 -05:00
github-actions[bot]
1bfa429c38 Automated version bumps (#7614)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-08-12 19:40:35 -05:00
Jonathan Bennett
ddd149945a More spoof remediation (#7612)
* More spoof remediation

* Fix signed comparison error

* Only fire self-bound messages into the routing module

* Update src/mesh/MeshModule.cpp

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

* String const

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-12 16:08:03 -05:00
Constantine
e3dd8164a4 nRF52840 promicro deepsleep fix with some additions (#7407)
* Pro-Micro DeepSleep Quick Fix
It is noticed that some nRF52840 boards (pro-micro in particular)
stopped waking up from the deep sleep state (shutdown state)
with a press of a button.
The problem is in a Serial1.end() call.

* Clear GPREGRET before setting
There are some troubles with that register:
it is recommended to clear it with 0xFF mask
and only after that perform a setting.

* Pro-Micro button SENSE signal
Added SENSE signal on the user button.
It is explicitly enabled now for this platform.

* nRF52 pre-sleep main serial check
Added another usage check for the main Serial.
It could save some nerves in case the port is not in use by any means.
Applied trunk fmt to the file.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-08-12 11:23:34 -05:00
Max
9b8149f14e Adding medium and large RU fonts. Fixing RU string width calculation (#7498)
* Adding  medium and large RU fonts. Fixing string width calculation for RU font

* Update src/graphics/draw/MessageRenderer.cpp

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

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-12 07:22:37 -05:00
renovate[bot]
05f1518951 chore(deps): update actions/download-artifact action to v5 (#7559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 21:47:21 -05:00
Jonathan Bennett
e26de85b5f Mark meshPackets based on which interface received. (#7589) 2025-08-11 21:47:04 -05:00
renovate[bot]
a2df80e833 chore(deps): update actions/checkout action to v5 (#7605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 20:58:54 -05:00
Ben Meadors
db238ef524 Log when this happened 2025-08-11 19:49:35 -05:00
Jonathan Bennett
f2b935f48f Stop the bleeding with malicious NodeDB overwrites (#7596) 2025-08-11 15:52:28 -05:00
Thomas Göttgens
e69da71d4e reorder for correct recognition (#7604) 2025-08-11 11:53:01 +02:00
Ben Meadors
7505fe7a7c Update device-ui deps 2025-08-09 10:38:09 -05:00
Ben Meadors
f6857f1bcb Heartbeat has a nonce now 2025-08-09 10:17:08 -05:00
github-actions[bot]
7fe2c74139 Update protobufs (#7588)
Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com>
2025-08-09 09:14:22 -05:00
github-actions[bot]
be60f9612e Update protobufs (#7587)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-08-09 08:14:04 -05:00
github-actions[bot]
2de9f015b1 Automated version bumps (#7586)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-08-09 08:12:24 -05:00
Ben Meadors
c1f4f79d4a Revert "128row display (#7511)"
This reverts commit d1f3c3c982.
2025-08-09 06:11:56 -05:00
renovate[bot]
7b874cf597 chore(deps): update meshtastic/device-ui digest to d044c01 (#7578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-08 11:31:49 -05:00
Jonathan Bennett
8568b56ac6 Fix a crash on Native reboot (#7570) 2025-08-07 12:28:01 -05:00
renovate[bot]
f2a880f813 chore(deps): update adafruit shtc3 to v1.0.2 (#7557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-07 13:56:17 +02:00
Jonathan Bennett
691327b2db Initial support for the ThinkNode M5 (#7502)
* Initial support for the ThinkNode M5

* Update variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini

Co-authored-by: Austin <vidplace7@gmail.com>

* Cleanup variant.h for Elecrow Thinknode M5

* Properly detect battery voltage

* Turn backlight off when screen sleeps

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Austin <vidplace7@gmail.com>
2025-08-07 06:28:15 -05:00
oscgonfer
a23c58c10a Avoid acquiring lock twice (#7555)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-08-06 06:38:36 -05:00
Jonathan Bennett
27c6b24e3a Rather than mysteriously rebooting, regenerate the keys and infrom the user. (#7558) 2025-08-05 19:53:25 -05:00
mrab
384436e937 fix: ina226 was not calibrated during init (#7547)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-08-05 06:34:52 -05:00
Jason P
eb30aae486 Create better log message for users (#7548) 2025-08-04 16:32:27 -05:00
Jason P
079286da04 Only toggle screen wake, don't break banners (#7545)
* Only toggle screen wake, don't break banners
* Fix code - only needed a small line change
2025-08-04 19:33:45 +02:00
tg-mw
0130899b3b Fix Melopero RV3028 RTC Settings (#7524) 2025-08-04 18:42:39 +02:00
Thomas Göttgens
d1f3c3c982 128row display (#7511)
* Fix 128 row monochrome display
* trunk fmt
* fix assignment
2025-08-04 17:25:31 +02:00
Jacob Powers
3b6eefa8bb feat: event mode - limit smart position updates to at most every 5m (#7505)
* feat: event mode - limit smart position updates to at most every 5m

* fix: convert 600 to 600000ms for 5min threshold

* fix: correct 5min threshold to 300000ms

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-31 21:45:34 -05:00
HarukiToreda
5107531425 Remember destination fix (#7427)
* T-watch screen misalignment fix

* Trunk fix

* Rember Last Receipient Node or channel

When a new freetext or preset message is sent and a destination is selected, the next message would forget the previously selected destination and would need to be selected again. With this fix it will remember the last destination selected until changed again.

* Fix for reply function to remember last messaged

* trunk check

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-31 07:36:03 -05:00
Ben Meadors
88655ffc44 Move BLE toggle menu option and add confirmation for canned messages in L1 (#7516)
* Move bluetooth to system menu and add confirmation for canned messages

* Cruft

* Handle else

* Warn

* Fixed screen reset
2025-07-31 07:34:41 -05:00
mikecarper
10bd10b9d1 bugfix Syntax error: "(" unexpected in device-update.sh (#7514)
* Update device-update.sh to use /bin/bash

* Update meshtasticd.postinst to use /bin/bash

* Update meshtasticd.postrm to use /bin/bash
2025-07-31 06:02:09 -05:00
renovate[bot]
956a0f102b Update platformio/ststm32 to v19.3.0 (#7512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 06:00:58 -05:00
Tom Fifield
bdedd0e1fe Airoha GPS - ignore estimated fixes (#7429)
TinyGPS Fix Quality has this information:
				0 - fix not available,
				1 - GPS fix,
				2 - Differential GPS fix (values above 2 are 2.3 features)
				3 = PPS fix
				4 = Real Time Kinematic
				5 = Float RTK
				6 = estimated (dead reckoning)
				7 = Manual input mode
				8 = Simulation mode

the previous Airoha code would allow quality >0 , which includes
estimated positions. These wouldn't be passed through to the mesh
due to other checks, but would affect the Airoha GPS_FIX_HOLD_TIME
calculations.

Changes the calculation to 1 >= quality <=5 .
2025-07-31 12:21:10 +10:00
Tom Fifield
4c901033b2 Workaround Webserver needing to stay up while Wifi is turned off (#7484)
Expertly triaged by @philon- , turning off wifi using the HTTP API
did not work. That was because we only served the HTTP API if Wifi
was deemed to be available, but mid-way through turning it off Wifi
was still available, but the configuration we were checking said it wasn't.

This patch introduces an additional way the system can determine if Wifi
is available, by referring to the WiFi.status(). This means that in that
limbo state where Wifi has been set to be turned off, but the configuration
has not been saved and it is still up, the HTTP API will stay up long enough
to save the configuration.

Fixes https://github.com/meshtastic/firmware/issues/6965
2025-07-30 07:47:00 -05:00
Ben Meadors
7d926da98c Heartbeat response (#7506)
* Heartbeat response

* Move it

* Add debug log for visibility
2025-07-30 07:40:27 -05:00
github-actions[bot]
1b793d1f23 Update protobufs (#7508)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-30 06:29:03 -05:00
Chloe Bethel
b5a8e8f51b DEBUG_MUTE correctness (#7492)
* treewide: make 'ifdef DEBUG_PORT' guards also take into account DEBUG_MUTE

* stm32wl: Add a guard against having debug prints turned on without PIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF defined

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-29 08:51:38 +10:00
Ben Meadors
cc5d00e211 Core portnums rebroadcast mode whitelist instead of blacklist (#7487) 2025-07-28 12:37:37 -05:00
Ben Meadors
1a8ab2aadc NodeDB count on MyNodeInfo for client progress reporting (#7489) 2025-07-28 12:23:59 -05:00
Thomas Göttgens
608fdc6f52 Santa may be checking his list twice, but we only need this in the platformio.ini (#7490) 2025-07-28 09:47:46 -05:00
rradillen
1d8638b47d [7353] Add all telemetry fields to json output (#7363)
* Serializer bugfix

* Remove duplicate test

* fix tests

* fix float precision issues

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-28 09:23:04 -05:00
Ben Meadors
3ecff48722 Set firmware edition (for events) from userprefs (#7488)
* Set firmware edition (for events) from userprefs

* Spaces in the right places
2025-07-28 07:31:33 -05:00
mikecarper
aa3b14ce72 bugfix Add rssi and snr to the store and forward code. (#7462)
* Update StoreForwardModule.cpp

* Update StoreForwardModule.h

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-27 20:03:01 -05:00
Ben Meadors
28aeb0f09e Validate Serial config console override modes (#7470)
* Validate serial config console override modes

* Update src/modules/SerialModule.cpp

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

* Disable

* Guard serial module

* Guards

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-26 19:55:54 -05:00
github-actions[bot]
7c5e2c5393 Update protobufs (#7473)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-26 16:21:49 -05:00
Tom Fifield
df8b629c2c Clear position on GPS deactivation, unless using fixed position (#7464)
* Clear position on GPS deactivation, unless using fixed position

As reported by @dreimal8 , and confirmed by @tuxmobil , when using
and then subsequently disabling GPS the last position retrieved from
the GPS was stored and continued to be broadcast.

This change introduces a check to see if we are transitioning from
GPS Enabled to GPS Disabled or Not Present. If we are, and fixed
position is not in use, then we clear the local position.

This will prevent inaccurate and undesired position broadcasts for those
who disable their GPS.

Fixes https://github.com/meshtastic/firmware/issues/7228

* Update triple click to also clear position

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-26 08:09:26 -05:00
Ben Meadors
a506dc6b65 Fix MQTT config bugs (#7446)
* Fix mqtt config bugs

* Apply suggestion from @Copilot

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

* Add client notification

* Verbiage

* Update src/mqtt/MQTT.cpp

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

* Update src/mqtt/MQTT.cpp

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

* Update src/mqtt/MQTT.cpp

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

* Update src/mqtt/MQTT.cpp

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

* Remove test that is no longer true

* This test no longer exists

* Fix client notification crap

* Suppress false positive

* Revert "Suppress false positive"

This reverts commit bead96eaee.

* Try macro exclusion

* Derp

* Fix

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jason P <applewiz@mac.com>
2025-07-26 05:38:24 -05:00
Iris
fc1e6ccb8c Rename Platformio.ini to platformio.ini (#7468) 2025-07-26 05:13:02 -05:00
Iris
bbc638ab82 Create Platformio.ini (#7450) 2025-07-25 15:36:37 -05:00
Austin
4f57a2e248 Build RP2350 (Pi Pico 2) (#7441) 2025-07-25 22:25:50 +02:00
Jason P
4c6db2c5bd Fix MHz label (#7455) 2025-07-25 08:10:35 -05:00
Pedro
bbe548bc98 Add BRT3 timezone option to TZPicker menu (#7438) 2025-07-24 22:42:42 -05:00
Pedro
d1fbf65c5d Fix timezone definition for UTC in TZPicker function (#7442) 2025-07-24 21:57:40 -05:00
Wilson
7a4a915312 Add Trace Route on BaseUI (#7386)
* Add TraceRoute function to menus and modules to support node path tracing

* Adjust text spacing and line wrapping logic in trace route result result.

* Add HAS_SCREEN for TraceRouteModule drawFrame.

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-24 17:23:45 -05:00
Austin
4f895f744b Take control of our PRs! (#7445) 2025-07-24 06:13:23 -05:00
Austin
66a831dfa8 Actions: Combine embedded builds // split by variant subdir (#7417) 2025-07-23 13:41:08 -04:00
Pedro
516597a73e Add NP_865 and BR_902 to device menu (#7434) 2025-07-23 14:56:22 +02:00
Tom Fifield
4eb6c9fb8e Add BR_902, Brazil 902MHz-907.5MHz (#7399)
As reported by @barbabarros , the Brazilian government has specific support for LoRA[1] across multiple frequencies[2][3].
We currently support Brazil through the ANZ/AU915 band. However, Brazil also has another frequency available for use:
902 - 907.5 MHz , 1W power limit, no duty cycle restrictions

[1]  https://sistemas.anatel.gov.br/anexar-api/publico/anexos/download/a028ab5cc4e3f97442830bba0c8bd1dd 
[2] 
https://informacoes.anatel.gov.br/legislacao/resolucoes/2025/2001-resolucao-772 
[3] https://informacoes.anatel.gov.br/legislacao/atos-de-certificacao-de-produtos/2017/1139-ato-14448#item10

Protobuf patch: https://github.com/meshtastic/protobufs/pull/737

Fixes https://github.com/meshtastic/firmware/issues/3741

Co-authored-by: Austin <vidplace7@gmail.com>
2025-07-23 14:55:17 +02:00
saiman pokhrel
46e2ae8860 Add Nepal 865 MHz to 868 MHz (#7380)
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Austin <vidplace7@gmail.com>
2025-07-23 14:54:43 +02:00
renovate[bot]
54c0cbeb66 Update meshtastic/device-ui digest to c75d545 (#7435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-23 07:46:34 -05:00
Jonathan Bennett
82ddf4732a Deprecate disable_triple_click config (#7425) 2025-07-23 05:57:17 -05:00
Austin
ed0cdefb44 Use platformio-core to build the matrix (#7424)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-22 21:01:29 -05:00
Tom Fifield
8836be0f47 AG3335 GPS: Use NAVIC in India/Nepal, L1+L5 elsewhere. (#7413)
As determined by @b8b8 , enabling NAVIC meant the more modern L5 GPS
signal was not used (L1 GPS is always available).

NAVIC, India's GNSS, probably provides the best coverage in India and
the neighbouring region. However, outside of NAVIC's coverage area, L5
GPS is highly desirable.

This patch amends the AG3335-family GPS configuration to enable L5 GPS
coverage by default. If the Lora region is set to India or Nepal,
NAVIC will be enabled instead.
2025-07-22 21:00:34 -05:00
github-actions[bot]
96f63f3945 Update protobufs (#7422)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-22 13:49:53 -05:00
Jonathan Bennett
d80dcd6afd Fix InkHUD shutdown code 2025-07-22 08:49:33 -05:00
Jonathan Bennett
2087629a47 Add a verbose mode flag to meshtasticd (#7416) 2025-07-22 06:22:23 -05:00
github-actions[bot]
878d68c5ef Upgrade trunk (#7420)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-07-22 06:22:07 -05:00
Jonathan Bennett
86960cdb1d Moves the shutdown thread into the Power class, make shutdown and reboot private (#7415) 2025-07-22 06:21:51 -05:00
Jonathan Bennett
fff12979a2 Set canned_message.enabled to true when setting defaults (#7414)
* Set canned_message.enabled to true when setting defaults

* Re-split canned messages on update
2025-07-21 19:31:07 -05:00
Austin
6c12baf4ed Migrate remaining variants to new dir structure (#7412) 2025-07-21 19:28:14 -05:00
whywilson
29449a71d4 When outputting RTTTL ringtones, you can still hear a periodic buzzing sound. This problem is fixed in this commit. 2025-07-21 14:44:41 -05:00
Austin
9b983b6487 nRF52840: Migrate variants to new structure (#7396) 2025-07-21 14:13:02 -05:00
Austin
806bfa54b5 Renovate: Use github-tags for XPowersLib updates (#7411) 2025-07-21 14:04:46 -05:00
Austin
920aeeeba5 Actions: pull_request_target is fun (#7398) 2025-07-21 14:03:13 -05:00
github-actions[bot]
32418448de Update protobufs (#7410)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2025-07-21 20:32:55 +02:00
Manuel
b3525c2569 T-Deck Pro support (#6936)
* initial draft

* fix touchscreen

* fix touchscreen

* optimize GPS

* battery management

* cleanup comments

* enable vibration motor

* refactored TCA8418Keyboard

* update HW_VENDOR id

* manual fixes after merge

* fix keyboard/BQ27220 detection

* add BQ27220

* modify charge voltage and current

* update XpowerLib

* design capacity

* try-fix charge behavior

* improve Vbus detection

* moved variant into esp32s3 folder

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2025-07-21 19:33:24 +02:00
github-actions[bot]
19dc2873c5 Upgrade trunk (#7400)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-07-21 20:07:45 +10:00
Austin
25b8d9b0ca ARCH_STM32*WL* macro fix (#7397) 2025-07-20 23:30:52 -04:00
Ben Meadors
8aef3c44f4 Text message rate limiting should return routing error instead (#7365)
* Text message rate limiting should return routing error instead

* Proper rooting

* Update PhoneAPI.cpp

* Update PhoneAPI.cpp
2025-07-20 20:12:10 -05:00
Ben Meadors
8345c21eff STM32 doesn't play 2025-07-20 20:02:32 -05:00
Jonathan Bennett
36b94cf823 Unify the shutdown proceedure (#7393)
* Unify the shutdown proceedure

* Don't double save nodeDB on shutdown

* Re-tool button shutdown to better correspond to tones

* Beep then save

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-20 18:53:40 -05:00
Austin
475cfe4af2 ESP32s3: Migrate variants to new structure (#7343) 2025-07-20 16:47:37 -05:00
Thomas Göttgens
b851b15a73 fix UDP builds on nRF (#7394)
* fix UDP builds on nRF
* fix rp2040 too
2025-07-20 23:13:50 +02:00
github-actions[bot]
73347c2542 Update protobufs (#7395)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-20 15:03:56 -05:00
Jason P
bc9023399d Restore High Resolution Hour Hand (#7392)
* Restore High Resolution Hour Hand

* Drop the int16_t
2025-07-20 13:43:54 -05:00
Thomas Göttgens
a9c9b96eb6 UDP for RAK4631 Eth Gw and the t-eth-elite. Solves #7149 (#7385)
* UDP for RAK4631 Eth Gw and the t-eth-elite. Also enable IP output on Portduino. Solves #7149

* Copilot suggestion

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

* fix portduino build

* initialize local port

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-20 06:22:00 -05:00
Austin
1c2a3c620f STM32: Migrate variants to new structure (#7389) 2025-07-20 06:21:17 -05:00
Austin
9313d04726 RP2040/RP2350: Migrate variants to new structure (#7345) 2025-07-20 06:20:57 -05:00
github-actions[bot]
44518fea14 Upgrade trunk (#7349)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-07-20 06:20:07 -05:00
Jonathan Bennett
91049d0db3 Misc cppcheck fixes (#7370) 2025-07-20 06:19:45 -05:00
Austin
855514b4f3 ESP32c3: Migrate variants to new structure (#7342) 2025-07-19 19:55:33 -05:00
Austin
974741a366 ESP32: Initial sort variants by platform (#7340) 2025-07-19 18:41:59 -05:00
Austin
5d98f7e307 Actions: Enforce PR labels (#7379) 2025-07-19 11:38:05 -05:00
github-actions[bot]
3ca45ae99c automated bumps (#7383)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-19 10:41:01 -05:00
Ben Meadors
cf574c71d8 Fix build 2025-07-18 09:24:34 -05:00
Tom Fifield
abe0a34fc0 Add additional Epoch check for time set (#7375)
We have two perhapsSetRTC functions, which are called to set the time.

The one with 3 parameters had a helpful check to reject an invalid time,
by comparing the time from the source against when the firmware was compiled.
The one with 2 parameters, which is called from the GPS lookForTime did not.

As a result, certain GPS with bad time handling could set a time that was
in the past.

This patch adds the same epoch check code to the other perhapsSetRTC method.

Fixes https://github.com/meshtastic/firmware/issues/7364
2025-07-18 05:49:19 -05:00
Jason P
71b6508ad3 BaseUI Updates (#7358)
* Calculate the length of the right string and use it

* Improve readability of Version Number

* Prevent negative message IDs and proactively favorite DM'd nodes

* Patch up Remove Favorite functionality

* Fix warnings for TFT_MESH_* and hasSupportBrightness

* Fix warning around casting variables

* Correct Favorite Node Behavior to rebuild favorite nodes when updated.

* Resolve bool kb_found issue not working for second discovery keyboards

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-07-16 19:44:23 -05:00
Thomas Göttgens
55fc4fcd90 clean up double i2c init/scan code (#7359) 2025-07-17 00:40:29 +02:00
Jonathan Bennett
c3b2b474c6 Drop NodeInfo packets if the is_licensed bit doesn't match owner (#7361) 2025-07-16 16:05:34 -05:00
Ben Meadors
39716ed1ba Fix L1 EInk HWModel (#7346) 2025-07-14 21:14:07 -05:00
Jason P
625a529f6c Message frame New Message Options and Clock / TDeck / Brightness Refinements (#7344)
* Clock updates and some TDeck corrections

* TDeck Brightness Works in TFT Builds

* Remove HAS_TFT from enabling Brightness, disable Brightness for TDeck

* Add default textMessage frame actions and adjust SharedUIDisplay

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-14 20:59:22 -05:00
Ben Meadors
31d56c16d5 Map report should work over devices which do not have network hardware (with client proxy) (#7341)
* Map report should work over devices which do not have network hardware (with client proxy)

* Fix else
2025-07-14 20:13:34 -05:00
Austin
5776385e8c STM32 PlatformIO cleanup (#7339) 2025-07-14 12:52:21 -05:00
github-actions[bot]
8f10de5684 [create-pull-request] automated change (#7338)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-14 09:46:43 -05:00
Daniel.Cao
e864fcf9a8 feat: add support for RAK Wismesh Tag hardware platform (#6853)
* add new platform rak_wismeshtag
* Remove RTC and Ethernet definitions from variant.h
* Remove unused EINK and Ethernet definitions from variant.h and platformio.ini
* Add WISMESH_TAG hardware model definition in architecture.h and update build flags in platformio.ini
* Remove unused build flags and dependencies
---------
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: daniel <daniel.cao@rakwireless.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2025-07-14 16:29:42 +02:00
Ben Meadors
86af5f5252 Trunk 2025-07-14 05:44:29 -05:00
Quency-D
daa1d582cb The screen display of the heltec wireless tracker is abnormal. (#7337)
The screen of the heltec wireless tracker uses the same power source as the GPS. If the GPS turns off the power during the screen shutdown period and then turns on the power, the screen will not function properly. So initialize the screen every time it starts.
2025-07-14 05:43:25 -05:00
github-actions[bot]
f197f0e5ec Upgrade trunk (#7336)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-07-14 05:12:52 -05:00
Chloe Bethel
3599ca6845 Add heap info via standard mallinfo() function for STM32 (#7327)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-14 05:12:38 -05:00
Chloe Bethel
1be4fc5ae9 GPS for STM32WL (#7297)
* Enable GPS for Wio-E5 variant on Serial2

* Add ability to override GPS serial port using GPS_SERIAL_PORT, and make RAK2560 use it.

* Don't try to send ATAK packets if ATAK is disabled, +4k flash
2025-07-14 05:12:26 -05:00
Thomas Göttgens
ac3e5684d6 get git url part from local repo (#7331) 2025-07-14 05:11:26 -05:00
Austin
29cca4d621 Revert "Actions: Move all Linux packaging into subdir (#7332)" (#7334)
This reverts commit f3ff80963a.
2025-07-13 20:54:52 -04:00
Austin
f3ff80963a Actions: Move all Linux packaging into subdir (#7332) 2025-07-13 20:48:17 -04:00
Austin
45e428eb25 PPA: Add Ubuntu Questing (25.10) to daily builds (#7329) 2025-07-13 16:22:42 -04:00
Thomas Göttgens
16d2650236 add pioenv to version string in debug log (#7328) 2025-07-13 19:16:14 +02:00
TSAO
2ecbf704d0 Improve OLED UI Responsiveness and Force Redraws for Canned message module (#7324)
* No delay between UI frame rendering for OLED

* force redraw the display

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jason P <applewiz@mac.com>
2025-07-13 08:28:05 -05:00
Styne13
5e28ee6d1e NodeDB.cpp: Fix iOS bluetooth crash by ensuring UINT32_MAX is not used (#7312)
Signed-off-by: Marcel <6253936+Styne13@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-13 06:26:35 -05:00
Neil Hanlon
622023de8b fix(device-update.sh): safely filter args without breaking parsing (#7305)
The previous method of removing `--change-mode` from the argument list used a
string (`NEW_ARGS`) and `eval set -- $NEW_ARGS` to reconstruct the positional
parameters. This was both unsafe and incorrect.

Because `NEW_ARGS` was built using quoted literal `$arg` strings instead of the
actual values, it resulted in all filtered arguments being set to the same last
value of `$arg`. This caused `getopts` to receive incorrect input and silently
fail to parse options like `-p` and `-f`, leading to broken behavior and unset
variables (e.g., `ESPTOOL_CMD` never got a port).

This patch rewrites the logic to use an array (`NEW_ARGS+=("$arg")`), and
resets positional parameters via `set -- "${NEW_ARGS[@]}"`. This preserves
argument integrity and avoids the unsafe use of `eval`.

Example of the broken behavior before this fix:

  ./device-update.sh -p /dev/ttyACM0 -f firmware.bin

Resulted in:

  set -- firmware.bin firmware.bin firmware.bin firmware.bin

Now:

  set -- -p /dev/ttyACM0 -f firmware.bin

as expected.

Signed-off-by: Neil Hanlon <neil@shrug.pw>
2025-07-13 06:19:58 -05:00
Andrew Yong
b49e59b904 xiao_ble README.md updates (#7283)
* docs(xiao_ble): Simplify building and flashing instructions

- **Update Bootloader** - deleted this section, as Meshtastic now builds-in a compatible SoftDevice
- **PlatformIO Environment Preparation** - deleted this section, as Meshtastic now builds-in a compatible SoftDevice
- **Build Meshtastic** - simplified it greatly by referring to Meshtastic documentation
- **Flash the firmware to the Xiao BLE** - simplified it greatly as Meshtastic now builds firmware.uf2; added some observations for a succesful flash

Light cleanup of Markdown and renumbering of sections.

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Replace some HTML with Markdown, cleanup Markdown

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Update SX126X_TXEN definition location

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Fresher information about E22 modules

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Instructions for E22...M33S modules

Also re-order the Build section to come after the Wiring section since the build instructions require special attention if the wiring/modules differ from the variant's expected pins/module.

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Rename all XIAO BLE to XIAO nRF52840

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Remove note about Linux since shell script is gone

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): trunk fmt and fix links

Signed-off-by: Andrew Yong <me@ndoo.sg>

---------

Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-07-13 06:17:50 -05:00
Andrew Yong
0133c5dc9e feat: New variant esp32c3_super_mini (#7133)
https://www.espboards.dev/esp32/esp32-c3-super-mini/

DIY build by @AntonKartajaya on Meshtastic Discord and a PCB version WIP by https://github.com/NomDeTom.

- I2C
  - I2C_SDA: 1
  - I2C_SCL: 0
  - OLED: SSD1306
- GPS
  - GPS_RX_PIN: 20
  - GPS_TX_PIN: 21
- Button
  - BUTTON_PIN: 9
- SPI
  - SCK: 10
  - MISO: 6
  - MOSI: 7
  - CS: 8
- LoRa: SX1262
  - LORA_RESET: 5
  - LORA_DIO1: 3
  - LORA_RXEN: 2
  - LORA_BUSY: 4

Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-07-13 06:12:24 -05:00
Andrew Yong
fd414ed149 feat: DIY Seeed XIAO nRF52840 + EBYTE E22 variants, pin-compatible with Wio-SX1262 kit (#7105)
These DIY builds are functionally similar to the legacy xiao_ble variant, but use a pinout harmonized with the officially-supported XIAO nRF52840 & Wio-SX1262 Kit for Meshtastic (SKU 102010710).

An additional E22-900M33S variant is provided to ensure SX1262 transmit power is set below the maximum PA input for that module, to avoid damaging it.

- seeed_xiao_nrf52840_e22_900m30s:
  - XIAO nRF52840 + EBYTE E22-900M30S
  - EBYTE E22 pinout matching Wio-SX1262 (SKU 113010003)
  - I2C
    - SDA: D6
    - SCL: D7
  - User button: D0
  - Gain is programmed in firmware - user Tx power setting is the desired final output power
- seeed_xiao_nrf52840_e22_900m33s:
  - XIAO nRF52840 + EBYTE E22-900M33S
  - EBYTE E22 pinout matching Wio-SX1262 (SKU 113010003)
  - I2C
    - SDA: D6
    - SCL: D7
  - User button: D0
  - Gain is programmed in firmware - user Tx power setting is the desired final output power

Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-07-13 15:58:01 +08:00
Austin
77768e9023 Remove Ubuntu oracular (#7322) 2025-07-13 12:39:20 +08:00
Austin
86be2ac12f userPrefs: Set default ringtone nag time (#7314) 2025-07-12 16:26:25 -05:00
Jonathan Bennett
4342d51f5a Bump Framework-native and set version string. (#7317) 2025-07-12 14:44:58 -05:00
Austin
41f52a6566 Build: Update platformio with pkg install (#7315) 2025-07-12 14:35:57 -05:00
Jonathan Bennett
cb47325f08 Seesaw Rotary (#7310)
* Initial add of Adafruit seesaw encoder

* Fully wire up seesaw

* Trunk

* Add #include configuration.h back to unbreak logging

* Tryfix the dumb compilation error

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-12 12:36:44 -05:00
Austin
deed6cd96a STM32: Properly ignore OneButton (#7311) 2025-07-12 06:27:45 -05:00
renovate[bot]
05c32c99e4 Update meshtastic/device-ui digest to 86a09a7 (#7308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 18:46:29 -05:00
Jonathan Bennett
1ca0584ba0 Add first config override for Native (#7306) 2025-07-11 16:09:59 -05:00
Ben Meadors
5ae8021aa6 I'm dumb 2025-07-11 08:28:21 -05:00
Ben Meadors
9798a91e7b Delete ringtone.proto file for factory reset (#7303) 2025-07-11 08:22:50 -05:00
Austin
e9a551ae90 Load ringtone from userPrefs (#7298)
* Load ringtone from userPrefs

* Apply suggestion from @Copilot

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

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-11 08:09:46 -05:00
Mictronics
d42bde135f Support native configuration Waveshare Pico LoRa module on Orange Pi Zero3 (#7295)
* Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28

* Merge PR #420

* Fixed double and missing Default class.

* Use correct format specifier and fixed typo.

* Removed duplicate code.

* Fix error: #if with no expression

* Fix warning: extra tokens at end of #endif directive.

* Fix antenna switching logic. Complementary-pin control logic is required on the rp2040-lora board.

* Fix deprecated macros.

* Set RP2040 in dormant mode when deep sleep is triggered.

* Fix array out of bounds read.

* Admin key count needs to be set otherwise the key will be zero loaded after reset.

* Don't reset the admin key size when loading defaults. Preserve an existing key in config if possible.

* Remove log spam when reading INA voltage sensor.

* Remove static declaration for admin keys from userPrefs.h. Load hard coded admin keys in case config file has empty slots.

* Removed newlines from log.

* Fix issue #5665.

* Fix build for Pico2 RP2350 platform.

* Enable Wifi client on Pico2W.

* Use correct processor on Pico2.

* Fix deprecated warning.

* Update platform and framework for RP2350.

* Added Pico2W variant including Wifi support.

* Fix typo in used variant.

* Remove obsolete define.

* Fix for native Linux build.

* Simplify RP2350 platform tag reference.

Co-authored-by: Austin <vidplace7@gmail.com>

* Cast user prefs strings.

* Update to last successfully building platform package.

* Define I2C GPIOs to ensure usage of both ports. Possibly fixes #5361

* RAK11310 support for RAK12002 RTC added.

* Update platform and framework packages to 4.4.3.

* Use RP2040 base platform and framework package. Use RAK11300 board definition in arduino-pico framework.

* Use RAK11300 board definition in arduino-pico framework.

* Fix build when MESHTASTIC_EXCLUDE_GPS is defined.

* Added configuration for Waveshare Pico LoRa module in combination with Orange Pi Zero3.

* Equal to upstream master.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Co-authored-by: Austin <vidplace7@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-07-11 06:54:37 -05:00
github-actions[bot]
72f3d19d5a Upgrade trunk (#7278)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-07-11 06:51:33 -05:00
renovate[bot]
f7ecf141b5 Update meshtastic/device-ui digest to 404c6e0 (#7302)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 06:45:18 -05:00
todd-herbert
1063ef9034 Shorter audio feedback for InkHUD buttons (#7301) 2025-07-11 17:30:48 +12:00
Jonathan Bennett
13ac182142 Pick up nodedb.h in Screen.cpp regardless of HAS_SCREEN state 2025-07-10 23:19:58 -05:00
Jonathan Bennett
4bab148e3b Make the shouldWake function always available, and remove the bool (#7300) 2025-07-10 22:51:43 -05:00
Jason P
be75f11156 Update Screen Wake Default Behavior (#7282)
* feat(display): enable screen wake on received messages

* feat(menu): add Screen Wakeup option in system menu

* feat(ui): update wake on message configuration and refactor save logic

* feat(TextMessageModule): conditionally trigger screen wake on received message

* Refactoring system menu options for notification and screen.

* Fix MUI options in the system menu.

* Build out Reboot/Shutdown Menu and consolidate options within it

* Trunk fixes

* Protobuf ref

* Revert generated files

* Update plumbing for screen_wakeup_menu

* Begin work on crafting a method to stop screen wake for received messages

* SharedUIDisplay.cpp doesn't need ExternalNotificationModule.h

* Stop screen wake if External Notification is enabled

* Removing extra log lines

* Add role and battery state checks for not waking screen. Menu updates to resolve some Back options not being linked

* Resolve some additional merge conflict related issues

* Shouldn't throttle the power menu

* Finalize renames of some menus

* Flip Flop MUI Menu to avoid accidental clicks

* NULL check for powerStatus

* Remove "Wakeup" eNum

* Update src/graphics/Screen.cpp

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

* CoPilot was close this should fix the builds

---------

Co-authored-by: whywilson <m.tools@qq.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-10 19:49:15 -05:00
renovate[bot]
093868f3ed Update dorny/test-reporter action to v2.1.1 (#7284)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 17:12:39 -05:00
renovate[bot]
fe534eae37 Update Adafruit BusIO to v1.17.2 (#7277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 17:12:25 -05:00
Kongduino
1aad442ccc Update platformio.ini (#7289)
The link to the product should point to the vendor's website, not a random distributor.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-07-10 17:11:19 -05:00
renovate[bot]
57c1c9286b Update RadioLib to v7.2.1 (#7287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 17:11:00 -05:00
Jonathan Bennett
6030bf50e0 Unbreak the macro 2025-07-10 11:40:02 -05:00
github-actions[bot]
6d8c815558 automated bumps (#7293)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-10 11:31:40 -05:00
Jonathan Bennett
5f5698ccc0 Explicitly include meshUtils.h 2025-07-10 10:29:33 -05:00
Jonathan Bennett
74c735d5fb Gate screen code behind IF_SCREEN() 2025-07-10 10:20:44 -05:00
Jonathan Bennett
107dec22bd Remove bogus validation check 2025-07-10 10:12:02 -05:00
Jonathan Bennett
0795b21c2b Key verification flow on BaseUI (#7240) 2025-07-10 09:45:36 -05:00
renovate[bot]
a7e516d6f6 Update Adafruit INA260 to v1.5.3 (#7270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 15:11:05 +08:00
Austin
f6d378255c Actions: Re-Add nrf52 hex release (rak4631) (#7276) 2025-07-08 23:53:51 -04:00
Austin
19d831d20d Whoops! Re-Add nRF52 OTA zips (#7275) 2025-07-08 21:33:59 -04:00
Austin
00495140bd GitHub Actions faster!! (#7268)
Use new meshtastic/gh-action-firmware Action

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-08 15:14:05 -05:00
Ben Meadors
354f149338 Make PacketHistory logging less chatty (#7272) 2025-07-08 15:12:44 -05:00
Jason P
999e1207a5 Show user which option is currently elected (#7271) 2025-07-08 14:38:38 -05:00
Jason P
916587c2a6 Update Bluetooth Toggle to match other variants (#7269) 2025-07-08 13:38:07 -05:00
todd-herbert
db4e4e6e53 Heltec Wireless Paper, VM-E213 Hardware Revisions (#7258)
* Tests to identify display model

* (InkHUD) SSD1682 controller IC
Has a few quirks, gets its own base class

* (InkHUD) E0213A367 Display
For Heltec Wireless Paper V1.1.1, V1.2
For Heltec VM-E213 V1.1

* (InkHUD) Select display model at boot

* (BaseUI) Wrapper to combine multiple GxEPD2 drivers
Workaround for issue of GxEPD2_BW objects not having a shared base class. Allows us to select a driver at runtime.
https://github.com/meshtastic/firmware/issues/6851#issuecomment-2905353447

* (BaseUI) Select E-Ink model at boot

* (InkHUD) SSD1682 deep sleep

* (InkHUD) No deep sleep for SSD1682

* (InkHUD) Fully no-op deep sleep for SSD1682
2025-07-08 13:01:48 -05:00
Jason P
9c08220d24 TFT_MESH Fixes Across Various Devices (#7247)
* Rename "r,g,b" variables to having a TFT_MESH_ prefix

* Reboot and then user options

* Restore TFT_MESH on any ST7789Spi driver
2025-07-08 06:24:12 -05:00
HarukiToreda
88b299dd41 Modules and favorite screen fix (#7264)
* T-watch screen misalignment fix

* Trunk fix

* Fix for favorite frame when module screen is enabled
2025-07-08 06:22:57 -05:00
github-actions[bot]
19af2d9e3b Upgrade trunk (#7266)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-07-08 06:22:24 -05:00
Jonathan Bennett
fa23be4424 Revert "GitHub Actions faster!! (#7244)" (#7262)
This reverts commit f2fb473ecf.
2025-07-07 19:50:44 -05:00
Ing. Jan Kaláb
e1f40c2db9 Fix install script (#7259)
This partially reverse 2ab717c (#7143), fixing the install script. It looks like a bad/missed copy/paste.
2025-07-07 19:36:21 -05:00
Ben Meadors
415dc4aa47 Try-fix: L76K spamming bad times can crash nodes (#7261)
* Try-fix: Clear GPS buffer when we encounter a bad time in NMEA

* Fix signed int warnings
2025-07-07 19:35:57 -05:00
Austin
f2fb473ecf GitHub Actions faster!! (#7244)
Use new meshtastic/gh-action-firmware Action

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-07 19:34:25 -05:00
Max
f95c77b8bd Fast fix, remove saving tx power inside limitPower() (#7255) 2025-07-07 07:50:13 -05:00
github-actions[bot]
09d4ee1ea7 Upgrade trunk (#7254)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-07-07 06:37:15 -05:00
Jonathan Bennett
40c586ca97 Automatically bail user out of displaymode_color when not HAS_TFT (#7248) 2025-07-06 16:36:22 -05:00
603 changed files with 9142 additions and 2612 deletions

View File

@@ -5,7 +5,7 @@ runs:
using: composite using: composite
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: recursive submodules: recursive
ref: ${{github.event.pull_request.head.ref}} ref: ${{github.event.pull_request.head.ref}}

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: recursive submodules: recursive
path: meshtasticd path: meshtasticd

View File

@@ -1,37 +0,0 @@
name: Build ESP32
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-esp32:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build ESP32
id: build
uses: ./.github/actions/build-variant
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
remove-debug-flags: >-
./arch/esp32/esp32.ini
./arch/esp32/esp32s2.ini
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini
./arch/esp32/esp32c6.ini
build-script-path: bin/build-esp32.sh
ota-firmware-source: firmware.bin
ota-firmware-target: release/bleota.bin
artifact-paths: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32

View File

@@ -1,37 +0,0 @@
name: Build ESP32-C3
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-esp32-c3:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build ESP32-C3
id: build
uses: ./.github/actions/build-variant
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
remove-debug-flags: >-
./arch/esp32/esp32.ini
./arch/esp32/esp32s2.ini
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini
./arch/esp32/esp32c6.ini
build-script-path: bin/build-esp32.sh
ota-firmware-source: firmware-c3.bin
ota-firmware-target: release/bleota-c3.bin
artifact-paths: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32c3

View File

@@ -1,37 +0,0 @@
name: Build ESP32-C6
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-esp32-c6:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build ESP32-C6
id: build
uses: ./.github/actions/build-variant
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
remove-debug-flags: >-
./arch/esp32/esp32.ini
./arch/esp32/esp32s2.ini
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini
./arch/esp32/esp32c6.ini
build-script-path: bin/build-esp32.sh
ota-firmware-source: firmware-c3.bin
ota-firmware-target: release/bleota-c3.bin
artifact-paths: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32c6

View File

@@ -1,37 +0,0 @@
name: Build ESP32-S3
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-esp32-s3:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build ESP32-S3
id: build
uses: ./.github/actions/build-variant
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
remove-debug-flags: >-
./arch/esp32/esp32.ini
./arch/esp32/esp32s2.ini
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini
./arch/esp32/esp32c6.ini
build-script-path: bin/build-esp32.sh
ota-firmware-source: firmware-s3.bin
ota-firmware-target: release/bleota-s3.bin
artifact-paths: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32s3

66
.github/workflows/build_firmware.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Build
on:
workflow_call:
inputs:
version:
required: true
type: string
platform:
required: true
type: string
pio_env:
required: true
type: string
permissions: read-all
jobs:
pio-build:
name: build-${{ inputs.platform }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Set OTA firmware source and target
if: startsWith(inputs.platform, 'esp32')
id: ota_dir
env:
PIO_PLATFORM: ${{ inputs.platform }}
run: |
if [ "$PIO_PLATFORM" = "esp32s3" ]; then
echo "src=firmware-s3.bin" >> $GITHUB_OUTPUT
echo "tgt=release/bleota-s3.bin" >> $GITHUB_OUTPUT
elif [ "$PIO_PLATFORM" = "esp32c3" ] || [ "$PIO_PLATFORM" = "esp32c6" ]; then
echo "src=firmware-c3.bin" >> $GITHUB_OUTPUT
echo "tgt=release/bleota-c3.bin" >> $GITHUB_OUTPUT
elif [ "$PIO_PLATFORM" = "esp32" ]; then
echo "src=firmware.bin" >> $GITHUB_OUTPUT
echo "tgt=release/bleota.bin" >> $GITHUB_OUTPUT
fi
- name: Build ${{ inputs.platform }}
id: build
uses: meshtastic/gh-action-firmware@main
with:
pio_platform: ${{ inputs.platform }}
pio_env: ${{ inputs.pio_env }}
pio_target: build
ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }}
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
overwrite: true
path: |
release/*.bin
release/*.elf
release/*.uf2
release/*.hex
release/*-ota.zip

View File

@@ -1,30 +0,0 @@
name: Build NRF52
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-nrf52:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build NRF52
id: build
uses: ./.github/actions/build-variant
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
build-script-path: bin/build-nrf52.sh
artifact-paths: |
release/*.hex
release/*.uf2
release/*.elf
release/*.zip
arch: nrf52840

View File

@@ -1,28 +0,0 @@
name: Build RPI2040
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-rpi2040:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Raspberry Pi 2040
id: build
uses: ./.github/actions/build-variant
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
build-script-path: bin/build-rpi2040.sh
artifact-paths: |
release/*.uf2
release/*.elf
arch: rp2040

View File

@@ -1,29 +0,0 @@
name: Build STM32
on:
workflow_call:
inputs:
board:
required: true
type: string
permissions: read-all
jobs:
build-stm32:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build STM32WL
id: build
uses: ./.github/actions/build-variant
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
build-script-path: bin/build-stm32.sh
artifact-paths: |
release/*.hex
release/*.bin
release/*.elf
arch: stm32

View File

@@ -30,7 +30,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
series: [plucky, oracular, noble, jammy] series:
- jammy # 22.04
- noble # 24.04
- plucky # 25.04
- questing # 25.10
uses: ./.github/workflows/package_ppa.yml uses: ./.github/workflows/package_ppa.yml
with: with:
ppa_repo: ppa:meshtastic/daily ppa_repo: ppa:meshtastic/daily

View File

@@ -47,7 +47,7 @@ jobs:
runs-on: ${{ inputs.runs-on }} runs-on: ${{ inputs.runs-on }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: recursive submodules: recursive
ref: ${{github.event.pull_request.head.ref}} ref: ${{github.event.pull_request.head.ref}}

View File

@@ -83,7 +83,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: recursive submodules: recursive
ref: ${{github.event.pull_request.head.ref}} ref: ${{github.event.pull_request.head.ref}}

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: recursive submodules: recursive
ref: ${{ github.ref }} ref: ${{ github.ref }}

View File

@@ -30,18 +30,31 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32, check] arch:
runs-on: ubuntu-latest - esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- check
runs-on: ubuntu-24.04
steps: steps:
- id: checkout - uses: actions/checkout@v5
uses: actions/checkout@v4 - uses: actions/setup-python@v5
name: Checkout base with:
- id: jsonStep python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
run: | run: |
if [[ "$GITHUB_HEAD_REF" == "" ]]; then if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
else else
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick) TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
fi fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
@@ -52,9 +65,25 @@ jobs:
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
rp2040: ${{ steps.jsonStep.outputs.rp2040 }} rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
stm32: ${{ steps.jsonStep.outputs.stm32 }} stm32: ${{ steps.jsonStep.outputs.stm32 }}
check: ${{ steps.jsonStep.outputs.check }} 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: check:
needs: setup needs: setup
strategy: strategy:
@@ -64,7 +93,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }} if: ${{ github.event_name != 'workflow_dispatch' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Build base - name: Build base
id: base id: base
uses: ./.github/actions/setup-base uses: ./.github/actions/setup-base
@@ -72,69 +101,95 @@ jobs:
run: bin/check-all.sh ${{ matrix.board }} run: bin/check-all.sh ${{ matrix.board }}
build-esp32: build-esp32:
needs: setup needs: [setup, version]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32) }} matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
uses: ./.github/workflows/build_esp32.yml uses: ./.github/workflows/build_firmware.yml
with: with:
board: ${{ matrix.board }} version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32
build-esp32-s3: build-esp32s3:
needs: setup needs: [setup, version]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_esp32_s3.yml uses: ./.github/workflows/build_firmware.yml
with: with:
board: ${{ matrix.board }} version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32s3
build-esp32-c3: build-esp32c3:
needs: setup needs: [setup, version]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_esp32_c3.yml uses: ./.github/workflows/build_firmware.yml
with: with:
board: ${{ matrix.board }} version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c3
build-esp32-c6: build-esp32c6:
needs: setup needs: [setup, version]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
uses: ./.github/workflows/build_esp32_c6.yml uses: ./.github/workflows/build_firmware.yml
with: with:
board: ${{ matrix.board }} version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c6
build-nrf52: build-nrf52840:
needs: setup needs: [setup, version]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_nrf52.yml uses: ./.github/workflows/build_firmware.yml
with: with:
board: ${{ matrix.board }} version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: nrf52840
build-rpi2040: build-rp2040:
needs: setup needs: [setup, version]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_rpi2040.yml uses: ./.github/workflows/build_firmware.yml
with: with:
board: ${{ matrix.board }} version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2040
build-rp2350:
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2350
build-stm32: build-stm32:
needs: setup needs: [setup, version]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.stm32) }} matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
uses: ./.github/workflows/build_stm32.yml uses: ./.github/workflows/build_firmware.yml
with: with:
board: ${{ matrix.board }} version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: stm32
build-debian-src: build-debian-src:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/build_debian_src.yml uses: ./.github/workflows/build_debian_src.yml
with: with:
series: UNRELEASED series: UNRELEASED
@@ -209,26 +264,36 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32] arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
[ [
version,
build-esp32, build-esp32,
build-esp32-s3, build-esp32s3,
build-esp32-c3, build-esp32c3,
build-esp32-c6, build-esp32c6,
build-nrf52, build-nrf52840,
build-rpi2040, build-rp2040,
build-rp2350,
build-stm32, build-stm32,
] ]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
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}}
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v5
with: with:
path: ./ path: ./
pattern: firmware-${{matrix.arch}}-* pattern: firmware-${{matrix.arch}}-*
@@ -237,17 +302,13 @@ jobs:
- name: Display structure of downloaded files - name: Display structure of downloaded files
run: ls -R run: ls -R
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Move files up - name: Move files up
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip - name: Repackage in single firmware zip
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true overwrite: true
path: | path: |
./firmware-*.bin ./firmware-*.bin
@@ -261,9 +322,9 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2 ./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30 retention-days: 30
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v5
with: with:
name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true merge-multiple: true
path: ./output path: ./output
@@ -277,12 +338,12 @@ jobs:
chmod +x ./output/device-update.sh chmod +x ./output/device-update.sh
- name: Zip firmware - name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./output run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip - name: Repackage in single elfs zip
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
overwrite: true overwrite: true
path: ./*.elf path: ./*.elf
retention-days: 30 retention-days: 30
@@ -290,8 +351,8 @@ jobs:
- uses: scruplelesswizard/comment-artifact@main - uses: scruplelesswizard/comment-artifact@main
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
with: with:
name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" 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 }} github-token: ${{ secrets.GITHUB_TOKEN }}
release-artifacts: release-artifacts:
@@ -300,56 +361,49 @@ jobs:
outputs: outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
needs: needs:
- version
- gather-artifacts - gather-artifacts
- build-debian-src - build-debian-src
- package-pio-deps-native-tft - package-pio-deps-native-tft
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: 3.x python-version: 3.x
- 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
- name: Create release - name: Create release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
id: create_release id: create_release
with: with:
draft: true draft: true
prerelease: true prerelease: true
name: Meshtastic Firmware ${{ steps.version.outputs.long }} Alpha name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ steps.version.outputs.long }} tag_name: v${{ needs.version.outputs.long }}
body: | body: |
Autogenerated by github action, developer should edit as required before publishing... Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb - name: Download source deb
uses: actions/download-artifact@v4 uses: actions/download-artifact@v5
with: with:
pattern: firmware-debian-${{ steps.version.outputs.deb }}~UNRELEASED-src pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true merge-multiple: true
path: ./output/debian-src path: ./output/debian-src
- name: Download `native-tft` pio deps - name: Download `native-tft` pio deps
uses: actions/download-artifact@v4 uses: actions/download-artifact@v5
with: with:
pattern: platformio-deps-native-tft-${{ steps.version.outputs.long }} pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true merge-multiple: true
path: ./output/pio-deps-native-tft path: ./output/pio-deps-native-tft
- name: Zip Linux sources - name: Zip Linux sources
working-directory: output working-directory: output
run: | run: |
zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
zip -9 -r ./platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip ./pio-deps-native-tft zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
# For diagnostics # For diagnostics
- name: Display structure of downloaded files - name: Display structure of downloaded files
@@ -359,8 +413,8 @@ jobs:
# Only run when targeting master branch with workflow_dispatch # Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }} if: ${{ github.ref_name == 'master' }}
run: | run: |
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd-${{ steps.version.outputs.deb }}-src.zip gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -368,26 +422,30 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32] arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }} if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-artifacts] needs: [release-artifacts, version]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: 3.x python-version: 3.x
- name: Get release version string - uses: actions/download-artifact@v5
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- uses: actions/download-artifact@v4
with: with:
pattern: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true merge-multiple: true
path: ./output path: ./output
@@ -400,16 +458,16 @@ jobs:
chmod +x ./output/device-update.sh chmod +x ./output/device-update.sh
- name: Zip firmware - name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./output run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v5
with: with:
name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
merge-multiple: true merge-multiple: true
path: ./elfs path: ./elfs
- name: Zip debug elfs - name: Zip debug elfs
run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./elfs run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
# For diagnostics # For diagnostics
- name: Display structure of downloaded files - name: Display structure of downloaded files
@@ -419,33 +477,30 @@ jobs:
# Only run when targeting master branch with workflow_dispatch # Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }} if: ${{ github.ref_name == 'master' }}
run: | run: |
gh release upload v${{ steps.version.outputs.long }} ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip gh release upload v${{ needs.version.outputs.long }} ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
gh release upload v${{ steps.version.outputs.long }} ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-firmware: publish-firmware:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: ${{ github.event_name == 'workflow_dispatch' }} if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-firmware] needs: [release-firmware, version]
env: env:
targets: esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,stm32 targets: |-
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: 3.x python-version: 3.x
- name: Get release version string - uses: actions/download-artifact@v5
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- uses: actions/download-artifact@v4
with: with:
pattern: firmware-{${{ env.targets }}}-${{ steps.version.outputs.long }} pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true merge-multiple: true
path: ./publish path: ./publish
@@ -459,9 +514,9 @@ jobs:
external_repository: meshtastic/meshtastic.github.io external_repository: meshtastic/meshtastic.github.io
publish_branch: master publish_branch: master
publish_dir: ./publish publish_dir: ./publish
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ steps.version.outputs.long }} destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
keep_files: true keep_files: true
user_name: github-actions[bot] user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com user_email: github-actions[bot]@users.noreply.github.com
commit_message: ${{ steps.version.outputs.long }} commit_message: ${{ needs.version.outputs.long }}
enable_jekyll: true enable_jekyll: true

View File

@@ -8,12 +8,13 @@ permissions: read-all
jobs: jobs:
trunk_check: trunk_check:
if: github.repository == 'meshtastic/firmware'
name: Trunk Check and Upload name: Trunk Check and Upload
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Trunk Check - name: Trunk Check
uses: trunk-io/trunk-action@v1 uses: trunk-io/trunk-action@v1
@@ -21,6 +22,7 @@ jobs:
trunk-token: ${{ secrets.TRUNK_TOKEN }} trunk-token: ${{ secrets.TRUNK_TOKEN }}
trunk_upgrade: trunk_upgrade:
if: github.repository == 'meshtastic/firmware'
# See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#automatic-upgrades # See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#automatic-upgrades
name: Trunk Upgrade (PR) name: Trunk Upgrade (PR)
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -29,7 +31,7 @@ jobs:
pull-requests: write # For trunk to create PRs pull-requests: write # For trunk to create PRs
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Trunk Upgrade - name: Trunk Upgrade
uses: trunk-io/trunk-action/upgrade@v1 uses: trunk-io/trunk-action/upgrade@v1

View File

@@ -34,7 +34,7 @@ jobs:
needs: build-debian-src needs: build-debian-src
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: recursive submodules: recursive
path: meshtasticd path: meshtasticd
@@ -58,7 +58,7 @@ jobs:
id: version id: version
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v5
with: with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true merge-multiple: true

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: recursive submodules: recursive
ref: ${{github.event.pull_request.head.ref}} ref: ${{github.event.pull_request.head.ref}}

View File

@@ -32,7 +32,7 @@ jobs:
needs: build-debian-src needs: build-debian-src
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: recursive submodules: recursive
path: meshtasticd path: meshtasticd
@@ -60,7 +60,7 @@ jobs:
id: version id: version
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v5
with: with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true merge-multiple: true

24
.github/workflows/pr_enforce_labels.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Check PR Labels
on:
pull_request:
types: [opened, edited, labeled, unlabeled, synchronize, reopened]
permissions:
pull-requests: read
contents: read
jobs:
check-label:
runs-on: ubuntu-24.04
steps:
- name: Check for PR labels
uses: actions/github-script@v7
with:
script: |
const labels = context.payload.pull_request.labels.map(label => label.name);
const requiredLabels = ['bugfix', 'enhancement', 'hardware-support', 'dependencies', 'submodules', 'github_actions', 'trunk'];
const hasRequiredLabel = labels.some(label => requiredLabels.includes(label));
if (!hasRequiredLabel) {
core.setFailed(`PR must have at least one of the following labels before it can be merged: ${requiredLabels.join(', ')}.`);
}

View File

@@ -20,7 +20,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
series: [plucky, oracular, noble, jammy] series:
- jammy # 22.04
- noble # 24.04
- plucky # 25.04
# - questing # 25.10
uses: ./.github/workflows/package_ppa.yml uses: ./.github/workflows/package_ppa.yml
with: with:
ppa_repo: |- ppa_repo: |-
@@ -56,7 +60,7 @@ jobs:
shell: bash shell: bash
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
@@ -99,8 +103,9 @@ jobs:
with: with:
base: ${{ github.event.repository.default_branch }} base: ${{ github.event.repository.default_branch }}
branch: create-pull-request/bump-version branch: create-pull-request/bump-version
labels: github_actions
title: Bump release version title: Bump release version
commit-message: automated bumps commit-message: Automated version bumps
add-paths: | add-paths: |
version.properties version.properties
debian/changelog debian/changelog

View File

@@ -13,6 +13,7 @@ permissions:
jobs: jobs:
semgrep-full: semgrep-full:
if: github.repository == 'meshtastic/firmware'
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
container: container:
image: semgrep/semgrep image: semgrep/semgrep
@@ -20,7 +21,7 @@ jobs:
steps: steps:
# step 1 # step 1
- name: clone application source code - name: clone application source code
uses: actions/checkout@v4 uses: actions/checkout@v5
# step 2 # step 2
- name: full scan - name: full scan

View File

@@ -13,7 +13,7 @@ jobs:
steps: steps:
# step 1 # step 1
- name: clone application source code - name: clone application source code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@@ -11,6 +11,7 @@ permissions:
jobs: jobs:
stale_issues: stale_issues:
if: github.repository == 'meshtastic/firmware'
name: Close Stale Issues name: Close Stale Issues
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -14,7 +14,7 @@ jobs:
name: Native Simulator Tests name: Native Simulator Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
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}}
@@ -70,7 +70,7 @@ jobs:
name: Native PlatformIO Tests name: Native PlatformIO Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
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}}
@@ -127,7 +127,7 @@ jobs:
- platformio-tests - platformio-tests
if: always() if: always()
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
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}}
@@ -137,20 +137,20 @@ jobs:
id: version id: version
- name: Download test artifacts - name: Download test artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v5
with: with:
name: platformio-test-report-${{ steps.version.outputs.long }}.zip name: platformio-test-report-${{ steps.version.outputs.long }}.zip
merge-multiple: true merge-multiple: true
- name: Test Report - name: Test Report
uses: dorny/test-reporter@v2.1.0 uses: dorny/test-reporter@v2.1.1
with: with:
name: PlatformIO Tests name: PlatformIO Tests
path: testreport.xml path: testreport.xml
reporter: java-junit reporter: java-junit
- name: Download coverage artifacts - name: Download coverage artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v5
with: with:
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
path: code-coverage-report path: code-coverage-report

View File

@@ -12,13 +12,15 @@ permissions:
jobs: jobs:
native-tests: native-tests:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/test_native.yml uses: ./.github/workflows/test_native.yml
hardware-tests: hardware-tests:
if: github.repository == 'meshtastic/firmware'
runs-on: test-runner runs-on: test-runner
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
# - uses: actions/setup-python@v5 # - uses: actions/setup-python@v5
# with: # with:

View File

@@ -18,7 +18,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Trunk Check - name: Trunk Check
uses: trunk-io/trunk-action@v1 uses: trunk-io/trunk-action@v1

View File

@@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Trunk Check - name: Trunk Check
uses: trunk-io/trunk-action@v1 uses: trunk-io/trunk-action@v1

View File

@@ -15,7 +15,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
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}}

View File

@@ -11,7 +11,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: true submodules: true
@@ -34,7 +34,9 @@ jobs:
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v7
with: with:
branch: create-pull-request/update-protobufs branch: create-pull-request/update-protobufs
labels: submodules
title: Update protobufs and classes title: Update protobufs and classes
commit-message: Update protobufs
add-paths: | add-paths: |
protobufs protobufs
src/mesh src/mesh

View File

@@ -1,6 +1,6 @@
version: 0.1 version: 0.1
cli: cli:
version: 1.24.0 version: 1.25.0
plugins: plugins:
sources: sources:
- id: trunk - id: trunk
@@ -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.447 - checkov@3.2.461
- renovate@41.17.2 - renovate@41.71.1
- prettier@3.6.2 - prettier@3.6.2
- trufflehog@3.89.2 - trufflehog@3.90.4
- yamllint@1.37.1 - yamllint@1.37.1
- bandit@1.8.5 - bandit@1.8.6
- trivy@0.64.0 - trivy@0.64.1
- taplo@0.9.3 - taplo@0.9.3
- ruff@0.12.1 - ruff@0.12.7
- isort@6.0.1 - isort@6.0.1
- markdownlint@0.45.0 - markdownlint@0.45.0
- oxipng@9.1.5 - oxipng@9.1.5
@@ -28,7 +28,7 @@ lint:
- shellcheck@0.10.0 - shellcheck@0.10.0
- black@25.1.0 - black@25.1.0
- git-diff-check - git-diff-check
- gitleaks@8.27.2 - gitleaks@8.28.0
- clang-format@16.0.3 - clang-format@16.0.3
ignore: ignore:
- linters: [ALL] - linters: [ALL]

View File

@@ -3,7 +3,7 @@
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions # trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
FROM python:3.13-bookworm AS builder FROM python:3.13-slim-trixie AS builder
ARG PIO_ENV=native ARG PIO_ENV=native
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC ENV TZ=Etc/UTC
@@ -36,7 +36,7 @@ RUN curl -L "https://github.com/meshtastic/web/releases/download/v$(cat /tmp/fir
##### PRODUCTION BUILD ############# ##### PRODUCTION BUILD #############
FROM debian:bookworm-slim FROM debian:trixie-slim
LABEL org.opencontainers.image.title="Meshtastic" \ LABEL org.opencontainers.image.title="Meshtastic" \
org.opencontainers.image.description="Debian Meshtastic daemon and web interface" \ org.opencontainers.image.description="Debian Meshtastic daemon and web interface" \
org.opencontainers.image.url="https://meshtastic.org" \ org.opencontainers.image.url="https://meshtastic.org" \
@@ -51,8 +51,8 @@ ENV TZ=Etc/UTC
USER root USER root
RUN apt-get update && apt-get --no-install-recommends -y install \ RUN apt-get update && apt-get --no-install-recommends -y install \
libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libuv1 libusb-1.0-0-dev \ libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
liborcania2.3 libulfius2.7 libssl3 \ liborcania2.3 libulfius2.7t64 libssl3t64 \
libx11-6 libinput10 libxkbcommon-x11-0 \ libx11-6 libinput10 libxkbcommon-x11-0 \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \ && apt-get clean && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /var/lib/meshtasticd \ && mkdir -p /var/lib/meshtasticd \

View File

@@ -54,8 +54,8 @@ lib_deps =
h2zero/NimBLE-Arduino@^1.4.3 h2zero/NimBLE-Arduino@^1.4.3
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
lewisxhe/XPowersLib@0.3.0 https://github.com/lewisxhe/XPowersLib/archive/v0.3.0.zip
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

View File

@@ -23,7 +23,7 @@ build_flags =
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
build_src_filter = build_src_filter =
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp> ${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp> -<serialization/>
lib_deps= lib_deps=
${arduino_base.lib_deps} ${arduino_base.lib_deps}

View File

@@ -2,7 +2,7 @@
[portduino_base] [portduino_base]
platform = platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop # renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
https://github.com/meshtastic/platform-native/archive/681ee029207e9fd040afa223df6e54074cbbe084.zip https://github.com/meshtastic/platform-native/archive/cd32f4ed20812d1fe9c8f74c0b6e80dc93dfce54.zip
framework = arduino framework = arduino
build_src_filter = build_src_filter =
@@ -17,7 +17,6 @@ build_src_filter =
+<mesh/raspihttp/> +<mesh/raspihttp/>
-<mesh/eth/> -<mesh/eth/>
-<modules/esp32> -<modules/esp32>
+<../variants/portduino>
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
@@ -30,14 +29,17 @@ lib_deps =
lovyan03/LovyanGFX@^1.2.0 lovyan03/LovyanGFX@^1.2.0
# renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
adafruit/Adafruit seesaw Library@1.7.9
build_flags = build_flags =
${arduino_base.build_flags} ${arduino_base.build_flags}
-D ARCH_PORTDUINO
-fPIC -fPIC
-Isrc/platform/portduino -Isrc/platform/portduino
-DRADIOLIB_EEPROM_UNSUPPORTED -DRADIOLIB_EEPROM_UNSUPPORTED
-DPORTDUINO_LINUX_HARDWARE -DPORTDUINO_LINUX_HARDWARE
-DHAS_UDP_MULTICAST -DHAS_UDP_MULTICAST=1
-lpthread -lpthread
-lstdc++fs -lstdc++fs
-lbluetooth -lbluetooth
@@ -48,4 +50,7 @@ build_flags =
-std=gnu17 -std=gnu17
-std=c++17 -std=c++17
lib_ignore = Adafruit NeoPixel lib_ignore =
Adafruit NeoPixel
Adafruit ST7735 and ST7789 Library
SD

View File

@@ -2,7 +2,7 @@
extends = arduino_base extends = arduino_base
platform = platform =
# renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32 # renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32
platformio/ststm32@19.2.0 platformio/ststm32@19.3.0
platform_packages = platform_packages =
# TODO renovate # TODO renovate
platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip
@@ -23,14 +23,20 @@ build_flags =
-DMESHTASTIC_EXCLUDE_SCREEN=1 -DMESHTASTIC_EXCLUDE_SCREEN=1
-DMESHTASTIC_EXCLUDE_MQTT=1 -DMESHTASTIC_EXCLUDE_MQTT=1
-DMESHTASTIC_EXCLUDE_BLUETOOTH=1 -DMESHTASTIC_EXCLUDE_BLUETOOTH=1
-DMESHTASTIC_EXCLUDE_GPS=1
-DMESHTASTIC_EXCLUDE_WIFI=1 -DMESHTASTIC_EXCLUDE_WIFI=1
-DMESHTASTIC_EXCLUDE_TZ=1 ; Exclude TZ to save some flash space. -DMESHTASTIC_EXCLUDE_TZ=1 ; Exclude TZ to save some flash space.
-DSERIAL_RX_BUFFER_SIZE=256 ; For GPS - the default of 64 is too small.
-DHAS_SCREEN=0 ; Always disable screen for STM32, it is not supported.
-DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF ; This is REQUIRED for at least traceroute debug prints - without it the length ends up uninitialized. -DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF ; This is REQUIRED for at least traceroute debug prints - without it the length ends up uninitialized.
;-DDEBUG_MUTE -DDEBUG_MUTE ; You can #undef DEBUG_MUTE in certain source files if you need the logs.
-fmerge-all-constants -fmerge-all-constants
-ffunction-sections -ffunction-sections
-fdata-sections -fdata-sections
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_LR11X0=1
-DHAL_DAC_MODULE_ONLY
-DHAL_RNG_MODULE_ENABLED
build_src_filter = build_src_filter =
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/RemoteHardwareModule.cpp> -<platform/nrf52> -<platform/portduino> -<platform/rp2xx0> -<mesh/raspihttp> ${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/RemoteHardwareModule.cpp> -<platform/nrf52> -<platform/portduino> -<platform/rp2xx0> -<mesh/raspihttp>
@@ -47,4 +53,4 @@ lib_deps =
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip
lib_ignore = lib_ignore =
mathertel/OneButton@2.6.1 OneButton

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1 platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.* rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ elif (echo $2 | grep -q "nrf52"); then
elif (echo $2 | grep -q "stm32"); then elif (echo $2 | grep -q "stm32"); then
bin/build-stm32.sh $1 bin/build-stm32.sh $1
elif (echo $2 | grep -q "rpi2040"); then elif (echo $2 | grep -q "rpi2040"); then
bin/build-rpi2040.sh $1 bin/build-rp2xx0.sh $1
else else
echo "Unknown target $2" echo "Unknown target $2"
exit 1 exit 1

View File

@@ -25,7 +25,7 @@ mkdir -p $OUTDIR/
rm -r $OUTDIR/* || true rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
pio pkg update --environment "$PIO_ENV" || platformioFailed pio pkg install --environment "$PIO_ENV" || platformioFailed
pio run --environment "$PIO_ENV" || platformioFailed pio run --environment "$PIO_ENV" || platformioFailed
cp ".pio/build/$PIO_ENV/program" "$OUTDIR/meshtasticd_linux_$(uname -m)" cp ".pio/build/$PIO_ENV/program" "$OUTDIR/meshtasticd_linux_$(uname -m)"
cp bin/native-install.* $OUTDIR cp bin/native-install.* $OUTDIR

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1 platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.* rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1 platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.* rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1 platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS" echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.* rm -f .pio/build/$1/firmware.*

View File

@@ -199,6 +199,10 @@ HostMetrics:
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString # UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
Config:
# DisplayMode: TWOCOLOR # uncomment to force BaseUI
# DisplayMode: COLOR # uncomment to force MUI
General: General:
MaxNodes: 200 MaxNodes: 200
MaxMessageQueue: 100 MaxMessageQueue: 100

View File

@@ -0,0 +1,52 @@
# https://www.waveshare.com/pico-lora-sx1262-868m.htm
# http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero-3.html
#
# See Orange Pi Zero3 manual, chapter 3.16, page 124 for 26-pin header pinout
#
# Pin Connection
# Waveshare Orange Pi Zero3
# 36 3.3V 17
# 15 MOSI 19
# 16 MISO 21
# 14 CLK 23
# 38 GND 25
# 4 BUSY 18
# 20 RESET 22
# 5 CS 24
# 26 DIO1/IRQ 26
Lora:
Module: sx1262 # Waveshare Raspberry Pico Lora module
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
# Specify either the spidev1_1 or the CS below, not both!
# On DietPi Linux, when using the user overlay dietpi-spi1_1.dtbo, CS will be configured with spidev1.1
spidev: spidev1.1 # See Orange Pi Zero3 manual, chapter 3.18.3, page 130
# CS: # CS PIN_24 -> chip 1, line 233
# pin: 24
# gpiochip: 1
# line: 233
SCK: # SCK PIN_23 -> chip 1, line 230
pin: 23
gpiochip: 1
line: 230
Busy: # BUSY PIN_18 -> chip 1, line 78
pin: 18
gpiochip: 1
line: 78
MOSI: # MOSI PIN_19 -> chip 1, line 231
pin: 19
gpiochip: 1
line: 231
MISO: # MISO PIN_21 -> chip 1, line 232
pin: 21
gpiochip: 1
line: 232
Reset: # NRST PIN_22 -> chip 1, line 71
pin: 22
gpiochip: 1
line: 71
IRQ: # DIO1 PIN_26 -> chip 1, line 74
pin: 26
gpiochip: 1
line: 74

View File

@@ -7,12 +7,7 @@ MCU=""
# Variant groups # Variant groups
BIGDB_8MB=( BIGDB_8MB=(
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly. "picomputer-s3"
if [[ $FILENAME == *"-tft-"* ]]; then
TFT_BUILD=true
fi
# Extract BASENAME from %FILENAME% for later use.r-s3"
"unphone" "unphone"
"seeed-sensecap-indicator" "seeed-sensecap-indicator"
"crowpanel-esp32s3" "crowpanel-esp32s3"

View File

@@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
PYTHON=${PYTHON:-$(which python3 python|head -n 1)} PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
CHANGE_MODE=false CHANGE_MODE=false
@@ -31,17 +31,16 @@ EOF
} }
# Check for --change-mode and remove it from arguments # Check for --change-mode and remove it from arguments
NEW_ARGS="" NEW_ARGS=()
for arg in "$@"; do for arg in "$@"; do
if [ "$arg" = "--change-mode" ]; then if [ "$arg" = "--change-mode" ]; then
CHANGE_MODE=true CHANGE_MODE=true
else else
NEW_ARGS="$NEW_ARGS \"\$arg\"" NEW_ARGS+=("$arg")
fi fi
done done
# Reset positional parameters to filtered list set -- "${NEW_ARGS[@]}"
eval set -- $NEW_ARGS
while getopts ":hp:P:f:" opt; do while getopts ":hp:P:f:" opt; do
case "${opt}" in case "${opt}" in

View File

@@ -2,50 +2,71 @@
"""Generate the CI matrix.""" """Generate the CI matrix."""
import configparser
import json import json
import os
import sys import sys
import random import random
import re
rootdir = "variants/" from platformio.project.config import ProjectConfig
options = sys.argv[1:] options = sys.argv[1:]
outlist = [] outlist = []
if len(options) < 1: if len(options) < 1:
print(json.dumps(outlist)) print(json.dumps(outlist))
exit() exit(1)
for subdir, dirs, files in os.walk(rootdir): cfg = ProjectConfig.get_instance()
for file in files: pio_envs = cfg.envs()
if file == "platformio.ini":
config = configparser.ConfigParser() # Gather all PlatformIO environments for filtering later
config.read(subdir + "/" + file) all_envs = []
for c in config.sections(): for pio_env in pio_envs:
if c.startswith("env:"): env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags')
section = config[c].name[4:] env_platform = None
if "extends" in config[config[c].name]: for flag in env_build_flags:
if options[0] + "_base" in config[config[c].name]["extends"]: # Extract the platform from the build flags
if "board_level" in config[config[c].name]: # Example flag: -I variants/esp32s3/heltec-v3
if ( match = re.search(r"-I\s?variants/([^/]+)", flag)
config[config[c].name]["board_level"] == "extra" if match:
) & ("extra" in options): env_platform = match.group(1)
outlist.append(section) break
else: # Intentionally fail if platform cannot be determined
outlist.append(section) if not env_platform:
# Add the TFT variants if the base variant is selected print(f"Error: Could not determine platform for environment '{pio_env}'")
elif section.replace("-tft", "") in outlist and config[config[c].name].get("board_level") != "extra": exit(1)
outlist.append(section) # Store env details as a dictionary, and add to 'all_envs' list
elif section.replace("-inkhud", "") in outlist and config[config[c].name].get("board_level") != "extra": env = {
outlist.append(section) 'name': pio_env,
if "board_check" in config[config[c].name]: 'platform': env_platform,
if (config[config[c].name]["board_check"] == "true") & ( 'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None),
"check" in options 'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False))
): }
outlist.append(section) all_envs.append(env)
if ("quick" in options) & (len(outlist) > 3):
print(json.dumps(random.sample(outlist, 3))) # Filter outputs based on options
# Check is mutually exclusive with other options (except 'pr')
if "check" in options:
for env in all_envs:
if env['board_check']:
if "pr" in options:
if env['board_level'] == 'pr':
outlist.append(env['name'])
else:
outlist.append(env['name'])
# Filter (non-check) builds by platform
else: else:
print(json.dumps(outlist)) for env in all_envs:
if options[0] == env['platform']:
# Always include board_level = 'pr'
if env['board_level'] == 'pr':
outlist.append(env['name'])
# Include board_level = 'extra' when requested
elif "extra" in options and env['board_level'] == "extra":
outlist.append(env['name'])
# If no board level is specified, include in release builds (not PR)
elif "pr" not in options and not env['board_level']:
outlist.append(env['name'])
# Return as a JSON list
print(json.dumps(outlist))

View File

@@ -87,6 +87,18 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="2.7.6" date="2025-08-12">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6</url>
</release>
<release version="2.7.5" date="2025-08-09">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.5</url>
</release>
<release version="2.7.4" date="2025-07-19">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.4</url>
</release>
<release version="2.7.3" date="2025-07-10">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.3</url>
</release>
<release version="2.7.2" date="2025-07-04"> <release version="2.7.2" date="2025-07-04">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.2</url> <url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.2</url>
</release> </release>

View File

@@ -3,6 +3,7 @@
# trunk-ignore-all(flake8/F821): For SConstruct imports # trunk-ignore-all(flake8/F821): For SConstruct imports
import sys import sys
from os.path import join from os.path import join
import subprocess
import json import json
import re import re
@@ -92,6 +93,17 @@ prefsLoc = projenv["PROJECT_DIR"] + "/version.properties"
verObj = readProps(prefsLoc) verObj = readProps(prefsLoc)
print("Using meshtastic platformio-custom.py, firmware version " + verObj["long"] + " on " + env.get("PIOENV")) print("Using meshtastic platformio-custom.py, firmware version " + verObj["long"] + " on " + env.get("PIOENV"))
# get repository owner if git is installed
try:
r_owner = (
subprocess.check_output(["git", "config", "--get", "remote.origin.url"])
.decode("utf-8")
.strip().split("/")
)
repo_owner = r_owner[-2] + "/" + r_owner[-1].replace(".git", "")
except subprocess.CalledProcessError:
repo_owner = "unknown"
jsonLoc = env["PROJECT_DIR"] + "/userPrefs.jsonc" jsonLoc = env["PROJECT_DIR"] + "/userPrefs.jsonc"
with open(jsonLoc) as f: with open(jsonLoc) as f:
jsonStr = re.sub("//.*","", f.read(), flags=re.MULTILINE) jsonStr = re.sub("//.*","", f.read(), flags=re.MULTILINE)
@@ -117,6 +129,7 @@ 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,
] + pref_flags ] + pref_flags
print ("Using flags:") print ("Using flags:")

View File

@@ -10,7 +10,8 @@
"hwids": [ "hwids": [
["0x239A", "0x4405"], ["0x239A", "0x4405"],
["0x239A", "0x0029"], ["0x239A", "0x0029"],
["0x239A", "0x002A"] ["0x239A", "0x002A"],
["0x2886", "0x1667"]
], ],
"usb_product": "HT-n5262", "usb_product": "HT-n5262",
"mcu": "nrf52840", "mcu": "nrf52840",

43
boards/t-deck-pro.json Normal file
View File

@@ -0,0 +1,43 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_qspi",
"partitions": "default_16MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "LilyGo T-Deck Pro S3 (16M Flash 8M QSPI PSRAM )",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"monitor": {
"speed": 115200
},
"url": "https://lilygo.cc/products/t-deck-pro",
"vendor": "LilyGo"
}

16
debian/changelog vendored
View File

@@ -1,4 +1,4 @@
meshtasticd (2.7.2.0) UNRELEASED; urgency=medium meshtasticd (2.7.6.0) UNRELEASED; urgency=medium
[ Austin Lane ] [ Austin Lane ]
* Initial packaging * Initial packaging
@@ -28,4 +28,16 @@ meshtasticd (2.7.2.0) UNRELEASED; urgency=medium
[ ] [ ]
* GitHub Actions Automatic version bump * GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Fri, 04 Jul 2025 11:58:01 +0000 [ Ubuntu ]
* 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> Tue, 12 Aug 2025 23:48:48 +0000

View File

@@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
# postinst script for meshtasticd # postinst script for meshtasticd
# #
# see: dh_installdeb(1) # see: dh_installdeb(1)

View File

@@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
# postrm script for meshtasticd # postrm script for meshtasticd
# #
# see: dh_installdeb(1) # see: dh_installdeb(1)

View File

@@ -6,7 +6,8 @@ default_envs = tbeam
extra_configs = extra_configs =
arch/*/*.ini arch/*/*.ini
variants/*/platformio.ini variants/*/*/platformio.ini
variants/*/diy/*/platformio.ini
src/graphics/niche/InkHUD/PlatformioConfig.ini src/graphics/niche/InkHUD/PlatformioConfig.ini
description = Meshtastic description = Meshtastic
@@ -60,8 +61,8 @@ 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/0119501e9983bd894830b02f545c377ee08d66fe.zip https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0119501e9983bd894830b02f545c377ee08d66fe.zip
# renovate: datasource=custom.pio depName=OneButton packageName=mathertel/library/OneButton # renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
mathertel/OneButton@2.6.1 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
https://github.com/meshtastic/arduino-fsm/archive/7db3702bf0cfe97b783d6c72595e3f38e0b19159.zip https://github.com/meshtastic/arduino-fsm/archive/7db3702bf0cfe97b783d6c72595e3f38e0b19159.zip
# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master # renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
@@ -101,21 +102,29 @@ lib_deps =
# renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog # renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog
arcao/Syslog@2.0.0 arcao/Syslog@2.0.0
; Minimal networking libs for nrf52 (excludes Syslog to save flash)
[nrf52_networking_base]
lib_deps =
# renovate: datasource=custom.pio depName=TBPubSubClient packageName=thingsboard/library/TBPubSubClient
thingsboard/TBPubSubClient@2.12.1
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
arduino-libraries/NTPClient@3.2.1
[radiolib_base] [radiolib_base]
lib_deps = lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib # renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
jgromes/RadioLib@7.2.0 jgromes/RadioLib@7.2.1
[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/8c7092c73425adfda1aac8c6960df06cd85f6d92.zip https://github.com/meshtastic/device-ui/archive/0cd108ff783539e41ef38258ba2784ab3b1bdc97.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.1 adafruit/Adafruit BusIO@1.17.2
# 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
@@ -129,7 +138,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library # renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library
adafruit/Adafruit MCP9808 Library@2.0.2 adafruit/Adafruit MCP9808 Library@2.0.2
# renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library # renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library
adafruit/Adafruit INA260 Library@1.5.2 adafruit/Adafruit INA260 Library@1.5.3
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219 # renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3 adafruit/Adafruit INA219@1.2.3
# renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor # renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor
@@ -177,7 +186,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X # renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X
adafruit/Adafruit MAX1704X@1.0.3 adafruit/Adafruit MAX1704X@1.0.3
# renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library # renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library
adafruit/Adafruit SHTC3 Library@1.0.1 adafruit/Adafruit SHTC3 Library@1.0.2
# renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X # renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X
adafruit/Adafruit LPS2X@2.0.6 adafruit/Adafruit LPS2X@2.0.6
# renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library # renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library

View File

@@ -89,14 +89,22 @@ class BluetoothStatus : public Status
case ConnectionState::CONNECTED: case ConnectionState::CONNECTED:
LOG_DEBUG("BluetoothStatus CONNECTED"); LOG_DEBUG("BluetoothStatus CONNECTED");
#ifdef BLE_LED #ifdef BLE_LED
#ifdef BLE_LED_INVERTED
digitalWrite(BLE_LED, LOW);
#else
digitalWrite(BLE_LED, HIGH); digitalWrite(BLE_LED, HIGH);
#endif
#endif #endif
break; break;
case ConnectionState::DISCONNECTED: case ConnectionState::DISCONNECTED:
LOG_DEBUG("BluetoothStatus DISCONNECTED"); LOG_DEBUG("BluetoothStatus DISCONNECTED");
#ifdef BLE_LED #ifdef BLE_LED
#ifdef BLE_LED_INVERTED
digitalWrite(BLE_LED, HIGH);
#else
digitalWrite(BLE_LED, LOW); digitalWrite(BLE_LED, LOW);
#endif
#endif #endif
break; break;
} }

View File

@@ -20,6 +20,11 @@
#include "meshUtils.h" #include "meshUtils.h"
#include "sleep.h" #include "sleep.h"
#if defined(ARCH_PORTDUINO)
#include "api/WiFiServerAPI.h"
#include "input/LinuxInputImpl.h"
#endif
// Working USB detection for powered/charging states on the RAK platform // Working USB detection for powered/charging states on the RAK platform
#ifdef NRF_APM #ifdef NRF_APM
#include "nrfx_power.h" #include "nrfx_power.h"
@@ -120,6 +125,15 @@ NullSensor max17048Sensor;
RAK9154Sensor rak9154Sensor; RAK9154Sensor rak9154Sensor;
#endif #endif
#ifdef HAS_PPM
// note: XPOWERS_CHIP_XXX must be defined in variant.h
#include <XPowersLib.h>
#endif
#ifdef HAS_BQ27220
#include "bq27220.h"
#endif
#ifdef HAS_PMU #ifdef HAS_PMU
XPowersLibInterface *PMU = NULL; XPowersLibInterface *PMU = NULL;
#else #else
@@ -665,6 +679,8 @@ bool Power::setup()
found = true; found = true;
} else if (lipoInit()) { } else if (lipoInit()) {
found = true; found = true;
} else if (lipoChargerInit()) {
found = true;
} else if (analogInit()) { } else if (analogInit()) {
found = true; found = true;
} }
@@ -679,9 +695,61 @@ bool Power::setup()
return found; return found;
} }
void Power::powerCommandsCheck()
{
if (rebootAtMsec && millis() > rebootAtMsec) {
LOG_INFO("Rebooting");
reboot();
}
if (shutdownAtMsec && millis() > shutdownAtMsec) {
shutdownAtMsec = 0;
shutdown();
}
}
void Power::reboot()
{
notifyReboot.notifyObservers(NULL);
#if defined(ARCH_ESP32)
ESP.restart();
#elif defined(ARCH_NRF52)
NVIC_SystemReset();
#elif defined(ARCH_RP2040)
rp2040.reboot();
#elif defined(ARCH_PORTDUINO)
deInitApiServer();
if (aLinuxInputImpl)
aLinuxInputImpl->deInit();
SPI.end();
Wire.end();
Serial1.end();
if (screen) {
delete screen;
screen = nullptr;
}
LOG_DEBUG("final reboot!");
::reboot();
#elif defined(ARCH_STM32WL)
HAL_NVIC_SystemReset();
#else
rebootAtMsec = -1;
LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied");
#endif
}
void Power::shutdown() void Power::shutdown()
{ {
LOG_INFO("Shutting Down");
#if HAS_SCREEN
if (screen) {
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
}
#endif
#if !defined(ARCH_STM32WL)
playShutdownMelody();
#endif
nodeDB->saveToDisk();
#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040) #if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
#ifdef PIN_LED1 #ifdef PIN_LED1
@@ -693,7 +761,11 @@ void Power::shutdown()
#ifdef PIN_LED3 #ifdef PIN_LED3
ledOff(PIN_LED3); ledOff(PIN_LED3);
#endif #endif
doDeepSleep(DELAY_FOREVER, false, false); doDeepSleep(DELAY_FOREVER, false, true);
#elif defined(ARCH_PORTDUINO)
exit(EXIT_SUCCESS);
#else
LOG_WARN("FIXME implement shutdown for this platform");
#endif #endif
} }
@@ -1237,3 +1309,144 @@ bool Power::lipoInit()
return false; return false;
} }
#endif #endif
#if defined(HAS_PPM) && HAS_PPM
/**
* Adapter class for BQ25896/BQ27220 Lipo battery charger.
*/
class LipoCharger : public HasBatteryLevel
{
private:
XPowersPPM *ppm = nullptr;
BQ27220 *bq = nullptr;
public:
/**
* Init the I2C BQ25896 Lipo battery charger
*/
bool runOnce()
{
if (ppm == nullptr) {
ppm = new XPowersPPM;
bool result = ppm->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
if (result) {
LOG_INFO("PPM BQ25896 init succeeded");
// Set the minimum operating voltage. Below this voltage, the PPM will protect
// ppm->setSysPowerDownVoltage(3100);
// Set input current limit, default is 500mA
// ppm->setInputCurrentLimit(800);
// Disable current limit pin
// ppm->disableCurrentLimitPin();
// Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV
ppm->setChargeTargetVoltage(4288);
// Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA
// ppm->setPrechargeCurr(64);
// The premise is that limit pin is disabled, or it will
// only follow the maximum charging current set by limit pin.
// Set the charging current , Range:0~5056mA ,step:64mA
ppm->setChargerConstantCurr(1024);
// To obtain voltage data, the ADC must be enabled first
ppm->enableMeasure();
// Turn on charging function
// If there is no battery connected, do not turn on the charging function
ppm->enableCharge();
} else {
LOG_WARN("PPM BQ25896 init failed");
delete ppm;
ppm = nullptr;
return false;
}
}
if (bq == nullptr) {
bq = new BQ27220;
bq->setDefaultCapacity(BQ27220_DESIGN_CAPACITY);
bool result = bq->init();
if (result) {
LOG_DEBUG("BQ27220 design capacity: %d", bq->getDesignCapacity());
LOG_DEBUG("BQ27220 fullCharge capacity: %d", bq->getFullChargeCapacity());
LOG_DEBUG("BQ27220 remaining capacity: %d", bq->getRemainingCapacity());
return true;
} else {
LOG_WARN("BQ27220 init failed");
delete bq;
bq = nullptr;
return false;
}
}
return false;
}
/**
* Battery state of charge, from 0 to 100 or -1 for unknown
*/
virtual int getBatteryPercent() override
{
return -1;
// return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated
}
/**
* The raw voltage of the battery in millivolts, or NAN if unknown
*/
virtual uint16_t getBattVoltage() override { return bq->getVoltage(); }
/**
* return true if there is a battery installed in this unit
*/
virtual bool isBatteryConnect() override { return ppm->getBattVoltage() > 0; }
/**
* return true if there is an external power source detected
*/
virtual bool isVbusIn() override { return ppm->getVbusVoltage() > 0; }
/**
* return true if the battery is currently charging
*/
virtual bool isCharging() override
{
bool isCharging = ppm->isCharging();
if (isCharging) {
LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull());
} else {
if (!ppm->isVbusIn()) {
LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity());
}
}
return isCharging;
}
};
LipoCharger lipoCharger;
/**
* Init the Lipo battery charger
*/
bool Power::lipoChargerInit()
{
bool result = lipoCharger.runOnce();
LOG_DEBUG("Power::lipoChargerInit lipo sensor is %s", result ? "ready" : "not ready yet");
if (!result)
return false;
batteryLevel = &lipoCharger;
return true;
}
#else
/**
* The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel
*/
bool Power::lipoChargerInit()
{
return false;
}
#endif

View File

@@ -72,7 +72,7 @@ extern Power *power;
static void shutdownEnter() static void shutdownEnter()
{ {
LOG_DEBUG("State: SHUTDOWN"); LOG_DEBUG("State: SHUTDOWN");
power->shutdown(); shutdownAtMsec = millis();
} }
#include "error.h" #include "error.h"

View File

@@ -47,10 +47,6 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
playComboTune(); // Ping sent feedback playComboTune(); // Ping sent feedback
break; break;
case INPUT_BROKER_SHUTDOWN:
playShutdownMelody(); // Shutdown feedback
break;
default: default:
// For other events, check if it's a printable character // For other events, check if it's a printable character
if (event->kbchar >= 32 && event->kbchar <= 126) { if (event->kbchar >= 32 && event->kbchar <= 126) {
@@ -69,10 +65,7 @@ int32_t BuzzerFeedbackThread::runOnce()
// This thread is primarily event-driven, but we can use runOnce // This thread is primarily event-driven, but we can use runOnce
// for any periodic tasks if needed in the future // for any periodic tasks if needed in the future
if (needsUpdate) { needsUpdate = false;
needsUpdate = false;
// Could add any periodic processing here
}
// Run every 100ms when active, less frequently when idle // Run every 100ms when active, less frequently when idle
return needsUpdate ? 100 : 1000; return needsUpdate ? 100 : 1000;

View File

@@ -140,6 +140,10 @@ bool playNextLeadUpNote()
playTones(&note, 1); // Play single note using existing playTones function playTones(&note, 1); // Play single note using existing playTones function
leadUpNoteIndex++; leadUpNoteIndex++;
if (leadUpNoteIndex >= leadUpNotesCount) {
return false; // this was the final note
}
return true; // Note was played (playTones handles buzzer availability internally) return true; // Note was played (playTones handles buzzer availability internally)
} }

View File

@@ -150,11 +150,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Define if screen should be mirrored left to right // Define if screen should be mirrored left to right
// #define SCREEN_MIRROR // #define SCREEN_MIRROR
// I2C Keyboards (M5Stack, RAK14004, T-Deck) // I2C Keyboards (M5Stack, RAK14004, T-Deck, T-Deck Pro, T-Lora Pager, CardKB, BBQ10, MPR121, TCA8418)
#define CARDKB_ADDR 0x5F #define CARDKB_ADDR 0x5F
#define TDECK_KB_ADDR 0x55 #define TDECK_KB_ADDR 0x55
#define BBQ10_KB_ADDR 0x1F #define BBQ10_KB_ADDR 0x1F
#define MPR121_KB_ADDR 0x5A #define MPR121_KB_ADDR 0x5A
#define TCA8418_KB_ADDR 0x34
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// SENSOR // SENSOR
@@ -193,8 +194,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MLX90614_ADDR_DEF 0x5A #define MLX90614_ADDR_DEF 0x5A
#define CGRADSENS_ADDR 0x66 #define CGRADSENS_ADDR 0x66
#define LTR390UV_ADDR 0x53 #define LTR390UV_ADDR 0x53
#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418 #define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418_KB
#define PCT2075_ADDR 0x37 #define PCT2075_ADDR 0x37
#define BQ27220_ADDR 0x55 // same address as TDECK_KB
#define BQ25896_ADDR 0x6B
#define LTR553ALS_ADDR 0x23
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ACCELEROMETER // ACCELEROMETER
@@ -208,6 +212,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BMX160_ADDR 0x69 #define BMX160_ADDR 0x69
#define ICM20948_ADDR 0x69 #define ICM20948_ADDR 0x69
#define ICM20948_ADDR_ALT 0x68 #define ICM20948_ADDR_ALT 0x68
#define BHI260AP_ADDR 0x28
#define BMM150_ADDR 0x13 #define BMM150_ADDR 0x13
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -230,6 +235,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Touchscreen // Touchscreen
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#define FT6336U_ADDR 0x48 #define FT6336U_ADDR 0x48
#define CST328_ADDR 0x1A
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected) // RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)

View File

@@ -74,7 +74,12 @@ class ScanI2C
RAK12035, RAK12035,
TCA8418KB, TCA8418KB,
PCT2075, PCT2075,
BMM150, CST328,
BQ25896,
BQ27220,
LTR553ALS,
BHI260AP,
BMM150
} DeviceType; } DeviceType;
// typedef uint8_t DeviceAddress; // typedef uint8_t DeviceAddress;

View File

@@ -184,8 +184,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = RTC_RV3028; type = RTC_RV3028;
logFoundDevice("RV3028", (uint8_t)addr.address); logFoundDevice("RV3028", (uint8_t)addr.address);
rtc.initI2C(*i2cBus); rtc.initI2C(*i2cBus);
rtc.writeToRegister(0x35, 0x07); // no Clkout // Update RTC EEPROM settings, if necessary
rtc.writeToRegister(0x37, 0xB4); if (rtc.readEEPROMRegister(0x35) != 0x07) {
rtc.writeEEPROMRegister(0x35, 0x07); // no Clkout
}
if (rtc.readEEPROMRegister(0x37) != 0xB4) {
rtc.writeEEPROMRegister(0x37, 0xB4);
}
break; break;
#endif #endif
@@ -206,7 +211,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
} }
break; break;
SCAN_SIMPLE_CASE(TDECK_KB_ADDR, TDECKKB, "T-Deck keyboard", (uint8_t)addr.address); case TDECK_KB_ADDR:
// Do we have the T-Deck keyboard or the T-Deck Pro battery sensor?
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x04), 1);
if (registerValue != 0) {
logFoundDevice("BQ27220", (uint8_t)addr.address);
type = BQ27220;
} else {
logFoundDevice("TDECKKB", (uint8_t)addr.address);
type = TDECKKB;
}
break;
SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10", (uint8_t)addr.address); SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address); SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address);
@@ -396,6 +411,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("BQ24295", (uint8_t)addr.address); logFoundDevice("BQ24295", (uint8_t)addr.address);
break; break;
} }
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x14), 1); // get ID
if ((registerValue & 0b00000011) == 0b00000010) {
type = BQ25896;
logFoundDevice("BQ25896", (uint8_t)addr.address);
break;
}
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); // get ID registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); // get ID
if (registerValue == 0x6A) { if (registerValue == 0x6A) {
type = LSM6DS3; type = LSM6DS3;
@@ -447,6 +468,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address); SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address); SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(LTR553ALS_ADDR, LTR553ALS, "LTR553ALS", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address); SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address); SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
#ifdef HAS_TPS65233 #ifdef HAS_TPS65233

View File

@@ -39,9 +39,9 @@ template <typename T, std::size_t N> std::size_t array_count(const T (&)[N])
return N; return N;
} }
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) #if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
#if defined(RAK2560) #if defined(GPS_SERIAL_PORT)
HardwareSerial *GPS::_serial_gps = &Serial2; HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
#else #else
HardwareSerial *GPS::_serial_gps = &Serial1; HardwareSerial *GPS::_serial_gps = &Serial1;
#endif #endif
@@ -643,8 +643,16 @@ bool GPS::setup()
delay(250); delay(250);
} else if (IS_ONE_OF(gnssModel, GNSS_MODEL_AG3335, GNSS_MODEL_AG3352)) { } else if (IS_ONE_OF(gnssModel, GNSS_MODEL_AG3335, GNSS_MODEL_AG3352)) {
_serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_IN ||
config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_NP_865) {
_serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC
// GPS GLONASS GALILEO BDS QZSS NAVIC
// 1 0 1 0 0 1
} else {
_serial_gps->write("$PAIR066,1,1,1,1,0,0*3A\r\n"); // Enable GPS+GLONASS+GALILEO+BDS
// GPS GLONASS GALILEO BDS QZSS NAVIC
// 1 1 1 1 0 0
}
// Configure NMEA (sentences will output once per fix) // Configure NMEA (sentences will output once per fix)
_serial_gps->write("$PAIR062,0,1*3F\r\n"); // GGA ON _serial_gps->write("$PAIR062,0,1*3F\r\n"); // GGA ON
_serial_gps->write("$PAIR062,1,0*3F\r\n"); // GLL OFF _serial_gps->write("$PAIR062,1,0*3F\r\n"); // GLL OFF
@@ -1503,7 +1511,7 @@ bool GPS::lookForTime()
#ifdef GNSS_AIROHA #ifdef GNSS_AIROHA
uint8_t fix = reader.fixQuality(); uint8_t fix = reader.fixQuality();
if (fix > 0) { if (fix >= 1 && fix <= 5) {
if (lastFixStartMsec > 0) { if (lastFixStartMsec > 0) {
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) { if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false; return false;
@@ -1536,7 +1544,10 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
if (t.tm_mon > -1) { if (t.tm_mon > -1) {
LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min, LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min,
t.tm_sec, ti.age()); t.tm_sec, ti.age());
perhapsSetRTC(RTCQualityGPS, t); if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) {
// Clear the GPS buffer if we got an invalid time
clearBuffer();
}
return true; return true;
} else } else
return false; return false;
@@ -1555,7 +1566,7 @@ bool GPS::lookForLocation()
#ifdef GNSS_AIROHA #ifdef GNSS_AIROHA
if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) {
uint8_t fix = reader.fixQuality(); uint8_t fix = reader.fixQuality();
if (fix > 0) { if (fix >= 1 && fix <= 5) {
if (lastFixStartMsec > 0) { if (lastFixStartMsec > 0) {
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) { if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false; return false;

View File

@@ -105,7 +105,7 @@ void readFromRTC()
* *
* If we haven't yet set our RTC this boot, set it from a GPS derived time * If we haven't yet set our RTC this boot, set it from a GPS derived time
*/ */
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate) RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
{ {
static uint32_t lastSetMsec = 0; static uint32_t lastSetMsec = 0;
uint32_t now = millis(); uint32_t now = millis();
@@ -113,7 +113,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
#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); LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return false; return RTCSetResultInvalidTime;
} }
#endif #endif
@@ -184,9 +184,9 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
readFromRTC(); readFromRTC();
#endif #endif
return true; return RTCSetResultSuccess;
} else { } else {
return false; return RTCSetResultNotSet; // RTC was already set with a higher quality time
} }
} }
@@ -215,7 +215,7 @@ const char *RtcName(RTCQuality quality)
* @param t The time to potentially set the RTC to. * @param t The time to potentially set the RTC to.
* @return True if the RTC was set to the provided time, false otherwise. * @return True if the RTC was set to the provided time, false otherwise.
*/ */
bool perhapsSetRTC(RTCQuality q, struct tm &t) RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
{ {
/* Convert to unix time /* Convert to unix time
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
@@ -226,12 +226,19 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
time_t res = gm_mktime(&t); time_t res = gm_mktime(&t);
struct timeval tv; struct timeval tv;
tv.tv_sec = res; tv.tv_sec = res;
tv.tv_usec = 0; // time.centisecond() * (10 / 1000); tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime;
}
#endif
// LOG_DEBUG("Got time from GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec); // LOG_DEBUG("Got time from GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec);
if (t.tm_year < 0 || t.tm_year >= 300) { if (t.tm_year < 0 || t.tm_year >= 300) {
// LOG_DEBUG("Ignore invalid GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec); // LOG_DEBUG("Ignore invalid GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec);
return false; return RTCSetResultInvalidTime;
} else { } else {
return perhapsSetRTC(q, &tv); return perhapsSetRTC(q, &tv);
} }

View File

@@ -22,13 +22,22 @@ enum RTCQuality {
RTCQualityGPS = 4 RTCQualityGPS = 4
}; };
/// The RTC set result codes
/// Used to indicate the result of an attempt to set the RTC.
enum RTCSetResult {
RTCSetResultNotSet = 0, ///< RTC was set successfully
RTCSetResultSuccess = 1, ///< RTC was set successfully
RTCSetResultInvalidTime = 3, ///< The provided time was invalid (e.g., before the build epoch)
RTCSetResultError = 4 ///< An error occurred while setting the RTC
};
RTCQuality getRTCQuality(); RTCQuality getRTCQuality();
extern uint32_t lastSetFromPhoneNtpOrGps; extern uint32_t lastSetFromPhoneNtpOrGps;
/// If we haven't yet set our RTC this boot, set it from a GPS derived time /// If we haven't yet set our RTC this boot, set it from a GPS derived time
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false); RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
bool perhapsSetRTC(RTCQuality q, struct tm &t); RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t);
/// Return a string name for the quality /// Return a string name for the quality
const char *RtcName(RTCQuality quality); const char *RtcName(RTCQuality quality);

View File

@@ -6,6 +6,10 @@
#include "main.h" #include "main.h"
#include <SPI.h> #include <SPI.h>
#ifdef GXEPD2_DRIVER_0
#include "einkDetect.h"
#endif
/* /*
The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini
Previously, these macros were defined at the top of this file. Previously, these macros were defined at the top of this file.
@@ -136,17 +140,32 @@ bool EInkDisplay::connect()
#endif #endif
#endif #endif
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) #if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
{ {
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1); auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel); adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
adafruitDisplay->init(); adafruitDisplay->init();
#ifdef ELECROW_ThinkNode_M1 #if defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
adafruitDisplay->setRotation(4); adafruitDisplay->setRotation(4);
#else #else
adafruitDisplay->setRotation(3); adafruitDisplay->setRotation(3);
#endif #endif
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
}
#elif defined(ELECROW_ThinkNode_M5)
{
// Start HSPI
hspi = new SPIClass(HSPI);
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
adafruitDisplay->init();
adafruitDisplay->setRotation(4);
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight); adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
} }
#elif defined(MESHLINK) #elif defined(MESHLINK)
@@ -174,9 +193,8 @@ bool EInkDisplay::connect()
} }
} }
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \ #elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || \
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \ defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
{ {
// Start HSPI // Start HSPI
hspi = new SPIClass(HSPI); hspi = new SPIClass(HSPI);
@@ -203,7 +221,7 @@ bool EInkDisplay::connect()
adafruitDisplay->setRotation(0); adafruitDisplay->setRotation(0);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
} }
#elif defined(M5_COREINK) #elif defined(M5_COREINK) || defined(T_DECK_PRO)
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY); auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel); adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0)); adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
@@ -232,6 +250,23 @@ bool EInkDisplay::connect()
adafruitDisplay->init(); adafruitDisplay->init();
adafruitDisplay->setRotation(3); adafruitDisplay->setRotation(3);
} }
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
// Detect display model, before starting SPI
EInkDetectionResult displayModel = detectEInk();
// Start HSPI
hspi = new SPIClass(HSPI);
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
// Create GxEPD2 object
adafruitDisplay = new GxEPD2_Multi<GXEPD2_DRIVER_0, GXEPD2_DRIVER_1>((uint8_t)displayModel, PIN_EINK_CS, PIN_EINK_DC,
PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
// Init GxEPD2
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
#endif #endif
return true; return true;

View File

@@ -5,6 +5,10 @@
#include "GxEPD2_BW.h" #include "GxEPD2_BW.h"
#include <OLEDDisplay.h> #include <OLEDDisplay.h>
#ifdef GXEPD2_DRIVER_0 // If variant has multiple possible display models
#include "GxEPD2Multi.h"
#endif
/** /**
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation. * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
* *
@@ -63,13 +67,20 @@ class EInkDisplay : public OLEDDisplay
// Connect to the display // Connect to the display
virtual bool connect() override; virtual bool connect() override;
// AdafruitGFX display object - instantiated in connect(), variant specific #ifdef GXEPD2_DRIVER_0
// AdafruitGFX display object - wrapper for multiple drivers
// Allows runtime detection of multiple displays
// Avoid this situation if possible!
GxEPD2_Multi<GXEPD2_DRIVER_0, GXEPD2_DRIVER_1> *adafruitDisplay = NULL;
#else
// AdafruitGFX display object (for single display model) - instantiated in connect(), variant specific
GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> *adafruitDisplay = NULL; GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> *adafruitDisplay = NULL;
#endif
// If display uses HSPI // If display uses HSPI
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ #if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \ defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || defined(ELECROW_ThinkNode_M5)
SPIClass *hspi = NULL; SPIClass *hspi = NULL;
#endif #endif

135
src/graphics/GxEPD2Multi.h Normal file
View File

@@ -0,0 +1,135 @@
// Wrapper class for GxEPD2_BW
// Generic signature at build-time, so that we can detect display model at run-time
// Workaround for issue of GxEPD2_BW objects not having a shared base class
// Only exposes methods which we are actually using
template <typename Driver0, typename Driver1> class GxEPD2_Multi
{
public:
void drawPixel(int16_t x, int16_t y, uint16_t color)
{
if (which == 0)
driver0->drawPixel(x, y, color);
else
driver1->drawPixel(x, y, color);
}
bool nextPage()
{
if (which == 0)
return driver0->nextPage();
else
return driver1->nextPage();
}
void hibernate()
{
if (which == 0)
driver0->hibernate();
else
driver1->hibernate();
}
void init(uint32_t serial_diag_bitrate = 0)
{
if (which == 0)
driver0->init(serial_diag_bitrate);
else
driver1->init(serial_diag_bitrate);
}
void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 20, bool pulldown_rst_mode = false)
{
if (which == 0)
driver0->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
else
driver1->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
}
void setRotation(uint8_t x)
{
if (which == 0)
driver0->setRotation(x);
else
driver1->setRotation(x);
}
void setPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
if (which == 0)
driver0->setPartialWindow(x, y, w, h);
else
driver1->setPartialWindow(x, y, w, h);
}
void setFullWindow()
{
if (which == 0)
driver0->setFullWindow();
else
driver1->setFullWindow();
}
int16_t width()
{
if (which == 0)
return driver0->width();
else
return driver1->width();
}
int16_t height()
{
if (which == 0)
return driver0->height();
else
return driver1->height();
}
void clearScreen(uint8_t value = 0xFF)
{
if (which == 0)
driver0->clearScreen();
else
driver1->clearScreen();
}
void endAsyncFull()
{
if (which == 0)
driver0->endAsyncFull();
else
driver1->endAsyncFull();
}
// Exposes methods of the GxEPD2_EPD object which is usually available as GxEPD2_BW::epd
class Epd2Wrapper
{
public:
bool isBusy() { return m_epd2->isBusy(); }
GxEPD2_EPD *m_epd2;
} epd2;
// Constructor
// Select driver by passing whichDriver as 0 or 1
GxEPD2_Multi(uint8_t whichDriver, int16_t cs, int16_t dc, int16_t rst, int16_t busy, SPIClass &spi)
{
assert(whichDriver == 0 || whichDriver == 1);
which = whichDriver;
LOG_DEBUG("GxEPD2_Multi driver: %d", which);
if (which == 0) {
driver0 = new GxEPD2_BW<Driver0, Driver0::HEIGHT>(Driver0(cs, dc, rst, busy, spi));
epd2.m_epd2 = &(driver0->epd2);
} else if (which == 1) {
driver1 = new GxEPD2_BW<Driver1, Driver1::HEIGHT>(Driver1(cs, dc, rst, busy, spi));
epd2.m_epd2 = &(driver1->epd2);
}
}
private:
uint8_t which;
GxEPD2_BW<Driver0, Driver0::HEIGHT> *driver0;
GxEPD2_BW<Driver1, Driver1::HEIGHT> *driver1;
};

View File

@@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "Screen.h" #include "Screen.h"
#include "NodeDB.h"
#include "PowerMon.h" #include "PowerMon.h"
#include "Throttle.h" #include "Throttle.h"
#include "configuration.h" #include "configuration.h"
@@ -44,7 +45,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif #endif
#include "FSCommon.h" #include "FSCommon.h"
#include "MeshService.h" #include "MeshService.h"
#include "NodeDB.h"
#include "RadioLibInterface.h" #include "RadioLibInterface.h"
#include "error.h" #include "error.h"
#include "gps/GeoCoord.h" #include "gps/GeoCoord.h"
@@ -171,7 +171,7 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
} }
// Called to trigger a banner with custom message and duration // Called to trigger a banner with custom message and duration
void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback) void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback)
{ {
#ifdef USE_EINK #ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
@@ -196,7 +196,6 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits,
std::function<void(uint32_t)> bannerCallback) std::function<void(uint32_t)> bannerCallback)
{ {
LOG_WARN("Show Number Picker");
#ifdef USE_EINK #ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
#endif #endif
@@ -294,13 +293,13 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
LOG_INFO("Protobuf Value uiconfig.screen_rgb_color: %d", uiconfig.screen_rgb_color); LOG_INFO("Protobuf Value uiconfig.screen_rgb_color: %d", uiconfig.screen_rgb_color);
int32_t rawRGB = uiconfig.screen_rgb_color; int32_t rawRGB = uiconfig.screen_rgb_color;
if (rawRGB > 0 && rawRGB <= 255255255) { if (rawRGB > 0 && rawRGB <= 255255255) {
uint8_t r = (rawRGB >> 16) & 0xFF; uint8_t TFT_MESH_r = (rawRGB >> 16) & 0xFF;
uint8_t g = (rawRGB >> 8) & 0xFF; uint8_t TFT_MESH_g = (rawRGB >> 8) & 0xFF;
uint8_t b = rawRGB & 0xFF; uint8_t TFT_MESH_b = rawRGB & 0xFF;
LOG_INFO("Values of r,g,b: %d, %d, %d", r, g, b); LOG_INFO("Values of r,g,b: %d, %d, %d", TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
if (r <= 255 && g <= 255 && b <= 255) { if (TFT_MESH_r <= 255 && TFT_MESH_g <= 255 && TFT_MESH_b <= 255) {
TFT_MESH = COLOR565(r, g, b); TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
} }
} }
@@ -313,8 +312,8 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
ST7789_MISO, ST7789_SCK); ST7789_MISO, ST7789_SCK);
#else #else
dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#endif #endif
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#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);
@@ -366,9 +365,6 @@ void Screen::doDeepSleep()
{ {
#ifdef USE_EINK #ifdef USE_EINK
setOn(false, graphics::UIRenderer::drawDeepSleepFrame); setOn(false, graphics::UIRenderer::drawDeepSleepFrame);
#ifdef PIN_EINK_EN
digitalWrite(PIN_EINK_EN, LOW); // power off backlight
#endif
#else #else
// Without E-Ink display: // Without E-Ink display:
setOn(false); setOn(false);
@@ -387,13 +383,19 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
PMU->enablePowerOutput(XPOWERS_ALDO2); PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif #endif
#ifdef HELTEC_TRACKER_V1_X
uint8_t tft_vext_enabled = digitalRead(VEXT_ENABLE);
#endif
#if !ARCH_PORTDUINO #if !ARCH_PORTDUINO
dispdev->displayOn(); dispdev->displayOn();
#endif #endif
#ifdef PIN_EINK_EN
if (uiconfig.screen_brightness == 1)
digitalWrite(PIN_EINK_EN, HIGH);
#elif defined(PCA_PIN_EINK_EN)
if (uiconfig.screen_brightness == 1)
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
#endif
#if defined(ST7789_CS) && \ #if defined(ST7789_CS) && \
!defined(M5STACK) // set display brightness when turning on screens. Just moved function from TFTDisplay to here. !defined(M5STACK) // set display brightness when turning on screens. Just moved function from TFTDisplay to here.
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness); static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
@@ -401,10 +403,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
dispdev->displayOn(); dispdev->displayOn();
#ifdef HELTEC_TRACKER_V1_X #ifdef HELTEC_TRACKER_V1_X
// If the TFT VEXT power is not enabled, initialize the UI. ui->init();
if (!tft_vext_enabled) {
ui->init();
}
#endif #endif
#ifdef USE_ST7789 #ifdef USE_ST7789
pinMode(VTFT_CTRL, OUTPUT); pinMode(VTFT_CTRL, OUTPUT);
@@ -426,11 +425,13 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead // eInkScreensaver parameter is usually NULL (default argument), default frame used instead
setScreensaverFrames(einkScreensaver); setScreensaverFrames(einkScreensaver);
#endif #endif
#ifdef ELECROW_ThinkNode_M1
if (digitalRead(PIN_EINK_EN) == HIGH) { #ifdef PIN_EINK_EN
digitalWrite(PIN_EINK_EN, LOW); digitalWrite(PIN_EINK_EN, LOW);
} #elif defined(PCA_PIN_EINK_EN)
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
#endif #endif
dispdev->displayOff(); dispdev->displayOff();
#ifdef USE_ST7789 #ifdef USE_ST7789
SPI1.end(); SPI1.end();
@@ -585,7 +586,7 @@ void Screen::setup()
touchScreenImpl1->init(); touchScreenImpl1->init();
} }
} }
#elif HAS_TOUCHSCREEN #elif HAS_TOUCHSCREEN && !defined(USE_EINK)
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();
@@ -641,6 +642,11 @@ void Screen::forceDisplay(bool forceUiUpdate)
// Tell EInk class to update the display // Tell EInk class to update the display
static_cast<EInkDisplay *>(dispdev)->forceDisplay(); static_cast<EInkDisplay *>(dispdev)->forceDisplay();
#else
// No delay between UI frame rendering
if (forceUiUpdate) {
setFastFramerate();
}
#endif #endif
} }
@@ -686,7 +692,7 @@ 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) {
menuHandler::LoraRegionPicker(0); menuHandler::OnboardMessage();
} }
#endif #endif
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) { if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
@@ -865,6 +871,8 @@ void Screen::setFrames(FrameFocus focus)
uint8_t previousFrameCount = framesetInfo.frameCount; uint8_t previousFrameCount = framesetInfo.frameCount;
FramesetInfo fsi; // Location of specific frames, for applying focus parameter FramesetInfo fsi; // Location of specific frames, for applying focus parameter
graphics::UIRenderer::rebuildFavoritedNodes();
LOG_DEBUG("Show standard frames"); LOG_DEBUG("Show standard frames");
showingNormalScreen = true; showingNormalScreen = true;
@@ -944,22 +952,6 @@ void Screen::setFrames(FrameFocus focus)
indicatorIcons.push_back(digital_icon_clock); indicatorIcons.push_back(digital_icon_clock);
#endif #endif
// We don't show the node info of our node (if we have it yet - we should)
size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
if (fsi.positions.firstFavorite == 255)
fsi.positions.firstFavorite = numframes;
fsi.positions.lastFavorite = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawNodeInfo;
indicatorIcons.push_back(icon_node);
}
}
#if HAS_WIFI && !defined(ARCH_PORTDUINO) #if HAS_WIFI && !defined(ARCH_PORTDUINO)
if (!dismissedFrames.wifi && isWifiAvailable()) { if (!dismissedFrames.wifi && isWifiAvailable()) {
fsi.positions.wifi = numframes; fsi.positions.wifi = numframes;
@@ -969,7 +961,7 @@ void Screen::setFrames(FrameFocus focus)
#endif #endif
// Beware of what changes you make in this code! // Beware of what changes you make in this code!
// We pass numfames into GetMeshModulesWithUIFrames() which is highly important! // We pass numframes into GetMeshModulesWithUIFrames() which is highly important!
// Inside of that callback, goes over to MeshModule.cpp and we run // Inside of that callback, goes over to MeshModule.cpp and we run
// modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr // modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr
// entries until we're ready to start building the matching entries. // entries until we're ready to start building the matching entries.
@@ -998,6 +990,34 @@ void Screen::setFrames(FrameFocus focus)
LOG_DEBUG("Added modules. numframes: %d", numframes); LOG_DEBUG("Added modules. numframes: %d", numframes);
// We don't show the node info of our node (if we have it yet - we should)
size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
// Temporary array to hold favorite node frames
std::vector<FrameCallback> favoriteFrames;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
}
}
// Insert favorite frames *after* collecting them all
if (!favoriteFrames.empty()) {
fsi.positions.firstFavorite = numframes;
for (const auto &f : favoriteFrames) {
normalFrames[numframes++] = f;
indicatorIcons.push_back(icon_node);
}
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
this->frameCount = numframes; // ✅ Save frame count for use in custom overlay this->frameCount = numframes; // ✅ Save frame count for use in custom overlay
LOG_DEBUG("Finished build frames. numframes: %d", numframes); LOG_DEBUG("Finished build frames. numframes: %d", numframes);
@@ -1009,8 +1029,7 @@ void Screen::setFrames(FrameFocus focus)
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
// just changed)
// Focus on a specific frame, in the frame set we just created // Focus on a specific frame, in the frame set we just created
switch (focus) { switch (focus) {
@@ -1247,8 +1266,12 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
devicestate.has_rx_text_message = true; // Needed to include the message frame devicestate.has_rx_text_message = true; // Needed to include the message frame
hasUnreadMessage = true; // Enables mail icon in the header hasUnreadMessage = true; // Enables mail icon in the header
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
forceDisplay(); // Forces screen redraw
// Only wake/force display if the configuration allows it
if (shouldWakeOnReceivedMessage()) {
setOn(true); // Wake up the screen first
forceDisplay(); // Forces screen redraw
}
// === Prepare banner content === // === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr; const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
@@ -1319,7 +1342,7 @@ int Screen::handleInputEvent(const InputEvent *event)
setFastFramerate(); // Draw ASAP setFastFramerate(); // Draw ASAP
#endif #endif
if (NotificationRenderer::isOverlayBannerShowing()) { if (NotificationRenderer::isOverlayBannerShowing()) {
NotificationRenderer::inEvent = event->inputEvent; NotificationRenderer::inEvent = *event;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP setFastFramerate(); // Draw ASAP
@@ -1359,9 +1382,12 @@ int Screen::handleInputEvent(const InputEvent *event)
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::LoraRegionPicker();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage && } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
devicestate.rx_text_message.from) { if (devicestate.rx_text_message.from) {
menuHandler::messageResponseMenu(); menuHandler::messageResponseMenu();
} else {
menuHandler::textMessageBaseMenu();
}
} 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 &&
this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) { this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
@@ -1413,3 +1439,23 @@ bool Screen::isOverlayBannerShowing()
#else #else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
#endif // HAS_SCREEN #endif // HAS_SCREEN
bool shouldWakeOnReceivedMessage()
{
/*
The goal here is to determine when we do NOT wake up the screen on message received:
- Any ext. notifications are turned on
- If role is not client / client_mute
- If the battery level is very low
*/
if (moduleConfig.external_notification.enabled) {
return false;
}
if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
return false;
}
if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
return false;
}
return true;
}

View File

@@ -26,6 +26,8 @@ struct BannerOverlayOptions {
}; };
} // namespace graphics } // namespace graphics
bool shouldWakeOnReceivedMessage();
#if !HAS_SCREEN #if !HAS_SCREEN
#include "power.h" #include "power.h"
namespace graphics namespace graphics
@@ -92,6 +94,7 @@ class Screen
#include "commands.h" #include "commands.h"
#include "concurrency/LockGuard.h" #include "concurrency/LockGuard.h"
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#include "graphics/draw/MenuHandler.h"
#include "input/InputBroker.h" #include "input/InputBroker.h"
#include "mesh/MeshModule.h" #include "mesh/MeshModule.h"
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
@@ -308,9 +311,15 @@ class Screen : public concurrency::OSThread
void showSimpleBanner(const char *message, uint32_t durationMs = 0); void showSimpleBanner(const char *message, uint32_t durationMs = 0);
void showOverlayBanner(BannerOverlayOptions); void showOverlayBanner(BannerOverlayOptions);
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback); void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback); void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
{
graphics::menuHandler::menuQueue = menuToShow;
runNow();
}
void startFirmwareUpdateScreen() void startFirmwareUpdateScreen()
{ {
ScreenCmd cmd; ScreenCmd cmd;

View File

@@ -16,7 +16,7 @@
#include "graphics/fonts/OLEDDisplayFontsCS.h" #include "graphics/fonts/OLEDDisplayFontsCS.h"
#endif #endif
#ifdef CROWPANEL_ESP32S3_5_EPAPER #if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
#include "graphics/fonts/EinkDisplayFonts.h" #include "graphics/fonts/EinkDisplayFonts.h"
#endif #endif
@@ -40,6 +40,9 @@
#ifdef OLED_PL #ifdef OLED_PL
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_PL // Height: 19 #define FONT_MEDIUM_LOCAL ArialMT_Plain_16_PL // Height: 19
#else #else
#ifdef OLED_RU
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_RU // Height: 19
#else
#ifdef OLED_UA #ifdef OLED_UA
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_UA // Height: 19 #define FONT_MEDIUM_LOCAL ArialMT_Plain_16_UA // Height: 19
#else #else
@@ -50,9 +53,13 @@
#endif #endif
#endif #endif
#endif #endif
#endif
#ifdef OLED_PL #ifdef OLED_PL
#define FONT_LARGE_LOCAL ArialMT_Plain_24_PL // Height: 28 #define FONT_LARGE_LOCAL ArialMT_Plain_24_PL // Height: 28
#else #else
#ifdef OLED_RU
#define FONT_LARGE_LOCAL ArialMT_Plain_24_RU // Height: 28
#else
#ifdef OLED_UA #ifdef OLED_UA
#define FONT_LARGE_LOCAL ArialMT_Plain_24_UA // Height: 28 #define FONT_LARGE_LOCAL ArialMT_Plain_24_UA // Height: 28
#else #else
@@ -63,6 +70,7 @@
#endif #endif
#endif #endif
#endif #endif
#endif
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS)) && \ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS)) && \
@@ -77,7 +85,7 @@
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28 #define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#endif #endif
#if defined(CROWPANEL_ESP32S3_5_EPAPER) #if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
#undef FONT_SMALL #undef FONT_SMALL
#undef FONT_MEDIUM #undef FONT_MEDIUM
#undef FONT_LARGE #undef FONT_LARGE

View File

@@ -206,7 +206,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
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 ===
int iconRightEdge = timeX - 1; int iconRightEdge = timeX - 2;
bool showMail = false; bool showMail = false;

View File

@@ -849,9 +849,29 @@ static LGFX *tft = nullptr;
#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp> #include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp>
#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp> #include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp>
class PanelInit_ST7701 : public lgfx::Panel_ST7701
{
public:
const uint8_t *getInitCommands(uint8_t listno) const override
{
// 180 degree hw rotation: vertical flip, horizontal flip
static constexpr const uint8_t list1[] = {0x36, 1, 0x10, // MADCTL for vertical flip
0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x10, // Command2 BK0 SEL
0xC7, 1, 0x04, // SDIR: X-direction Control (Horizontal Flip)
0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x00, // Command2 BK0 DIS
0xFF, 0xFF};
switch (listno) {
case 1:
return list1;
default:
return lgfx::Panel_ST7701::getInitCommands(listno);
}
}
};
class LGFX : public lgfx::LGFX_Device class LGFX : public lgfx::LGFX_Device
{ {
lgfx::Panel_ST7701 _panel_instance; PanelInit_ST7701 _panel_instance;
lgfx::Bus_RGB _bus_instance; lgfx::Bus_RGB _bus_instance;
lgfx::Light_PWM _light_instance; lgfx::Light_PWM _light_instance;
lgfx::Touch_FT5x06 _touch_instance; lgfx::Touch_FT5x06 _touch_instance;
@@ -1184,9 +1204,9 @@ bool TFTDisplay::connect()
attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING); attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING);
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2) #elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2)
tft->setRotation(1); // T-Deck has the TFT in landscape tft->setRotation(1); // T-Deck has the TFT in landscape
#elif defined(T_WATCH_S3) || defined(SENSECAP_INDICATOR) #elif defined(T_WATCH_S3)
tft->setRotation(2); // T-Watch S3 left-handed orientation tft->setRotation(2); // T-Watch S3 left-handed orientation
#elif ARCH_PORTDUINO #elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR)
tft->setRotation(0); // use config.yaml to set rotation tft->setRotation(0); // use config.yaml to set rotation
#else #else
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label

View File

@@ -186,7 +186,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
{ {
display->clear(); display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT); display->setTextAlignment(TEXT_ALIGN_LEFT);
int line = 1;
// === Set Title, Blank for Clock // === Set Title, Blank for Clock
const char *titleStr = ""; const char *titleStr = "";
// === Header === // === Header ===
@@ -218,7 +218,6 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
hour %= 12; hour %= 12;
if (hour == 0) if (hour == 0)
hour = 12; hour = 12;
bool isPM = hour >= 12;
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute); snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
} else { } else {
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute); snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
@@ -230,6 +229,8 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
float scale = 1.5; float scale = 1.5;
#elif defined(CHATTER_2)
float scale = 1.1;
#else #else
float scale = 0.75; float scale = 0.75;
if (isHighResolution) { if (isHighResolution) {
@@ -285,6 +286,9 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
int yOffset = (isHighResolution) ? 3 : 1; int yOffset = (isHighResolution) ? 3 : 1;
#ifdef SENSECAP_INDICATOR #ifdef SENSECAP_INDICATOR
yOffset -= 3; yOffset -= 3;
#endif
#ifdef T_DECK
yOffset -= 5;
#endif #endif
if (config.display.use_12h_clock) { if (config.display.use_12h_clock) {
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2, display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
@@ -362,7 +366,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// hour hand radius and y coordinate // hour hand radius and y coordinate
int16_t hourHandRadius = radius * 0.35; int16_t hourHandRadius = radius * 0.35;
if (isHighResolution) { if (isHighResolution) {
int16_t hourHandRadius = radius * 0.55; hourHandRadius = radius * 0.55;
} }
int16_t hourHandNoonY = centerY - hourHandRadius; int16_t hourHandNoonY = centerY - hourHandRadius;
@@ -381,7 +385,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
bool isPM = hour >= 12; bool isPM = hour >= 12;
if (config.display.use_12h_clock) { if (config.display.use_12h_clock) {
bool isPM = hour >= 12; isPM = hour >= 12;
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
int yOffset = isHighResolution ? 1 : 0; int yOffset = isHighResolution ? 1 : 0;
#ifdef USE_EINK #ifdef USE_EINK

View File

@@ -412,9 +412,9 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
float freq = RadioLibInterface::instance->getFreq(); float freq = RadioLibInterface::instance->getFreq();
snprintf(freqStr, sizeof(freqStr), "%.3f", freq); snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
if (config.lora.channel_num == 0) { if (config.lora.channel_num == 0) {
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %smhz", freqStr); snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
} else { } 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);
} }
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) {
@@ -483,7 +483,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
} }
// **************************** // ****************************
// * Memory Screen * // * System Screen *
// **************************** // ****************************
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{ {
@@ -593,7 +593,19 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
} }
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 *lastDot = strrchr(appversionstr, '.');
if (lastDot) {
size_t prefixLen = lastDot - appversionstr;
strncpy(appversionstr_formatted, appversionstr, prefixLen);
appversionstr_formatted[prefixLen] = '\0';
strncat(appversionstr_formatted, " (", sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
strncat(appversionstr_formatted, lastDot + 1, sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
strncat(appversionstr_formatted, ")", sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
appversionstr[sizeof(appversionstr) - 1] = '\0';
}
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);

View File

@@ -13,6 +13,10 @@
#include "main.h" #include "main.h"
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h" #include "modules/CannedMessageModule.h"
#include "modules/KeyVerificationModule.h"
#include "modules/TraceRouteModule.h"
#include <functional>
extern uint16_t TFT_MESH; extern uint16_t TFT_MESH;
@@ -22,6 +26,27 @@ 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::OnboardMessage()
{
static const char *optionsArray[] = {"OK", "Got it!"};
enum optionsNumbers { OK, got };
BannerOverlayOptions bannerOptions;
#if HAS_TFT
bannerOptions.message = "Welcome to Meshtastic!\nSwipe to navigate and\nlong press to select\nor open a menu.";
#elif defined(BUTTON_PIN)
bannerOptions.message = "Welcome to Meshtastic!\nClick to navigate and\nlong press to select\nor open a menu.";
#else
bannerOptions.message = "Welcome to Meshtastic!\nUse the Select button\nto open menus\nand make selections.";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
menuHandler::menuQueue = menuHandler::no_timeout_lora_picker;
screen->runNow();
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::LoraRegionPicker(uint32_t duration) void menuHandler::LoraRegionPicker(uint32_t duration)
{ {
static const char *optionsArray[] = {"Back", static const char *optionsArray[] = {"Back",
@@ -50,12 +75,14 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
"PH_915", "PH_915",
"ANZ_433", "ANZ_433",
"KZ_433", "KZ_433",
"KZ_863"}; "KZ_863",
"NP_865",
"BR_902"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Set the LoRa region"; bannerOptions.message = "Set the LoRa region";
bannerOptions.durationMs = duration; bannerOptions.durationMs = duration;
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 25; bannerOptions.optionsCount = 27;
bannerOptions.InitialSelected = 0; bannerOptions.InitialSelected = 0;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) { if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) {
@@ -114,6 +141,22 @@ void menuHandler::TwelveHourPicker()
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
// Reusable confirmation prompt function
void menuHandler::showConfirmationBanner(const char *message, std::function<void()> onConfirm)
{
static const char *confirmOptions[] = {"No", "Yes"};
BannerOverlayOptions confirmBanner;
confirmBanner.message = message;
confirmBanner.optionsArrayPtr = confirmOptions;
confirmBanner.optionsCount = 2;
confirmBanner.bannerCallback = [onConfirm](int confirmSelected) -> void {
if (confirmSelected == 1) {
onConfirm();
}
};
screen->showOverlayBanner(confirmBanner);
}
void menuHandler::ClockFacePicker() void menuHandler::ClockFacePicker()
{ {
static const char *optionsArray[] = {"Back", "Digital", "Analog"}; static const char *optionsArray[] = {"Back", "Digital", "Analog"};
@@ -128,14 +171,15 @@ void menuHandler::ClockFacePicker()
screen->runNow(); screen->runNow();
} else if (selected == Digital) { } else if (selected == Digital) {
uiconfig.is_clockface_analog = false; uiconfig.is_clockface_analog = false;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK); screen->setFrames(Screen::FOCUS_CLOCK);
} else { } else {
uiconfig.is_clockface_analog = true; uiconfig.is_clockface_analog = true;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK); screen->setFrames(Screen::FOCUS_CLOCK);
} }
}; };
bannerOptions.InitialSelected = uiconfig.is_clockface_analog ? 2 : 1;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
@@ -149,6 +193,7 @@ void menuHandler::TZPicker()
"US/Mountain", "US/Mountain",
"US/Central", "US/Central",
"US/Eastern", "US/Eastern",
"BR/Brasilia",
"UTC", "UTC",
"EU/Western", "EU/Western",
"EU/" "EU/"
@@ -163,7 +208,7 @@ void menuHandler::TZPicker()
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Pick Timezone"; bannerOptions.message = "Pick Timezone";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 17; bannerOptions.optionsCount = 19;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) { if (selected == 0) {
menuHandler::menuQueue = menuHandler::clock_menu; menuHandler::menuQueue = menuHandler::clock_menu;
@@ -182,25 +227,27 @@ void menuHandler::TZPicker()
strncpy(config.device.tzdef, "CST6CDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "CST6CDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef));
} else if (selected == 7) { // Eastern } else if (selected == 7) { // Eastern
strncpy(config.device.tzdef, "EST5EDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "EST5EDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef));
} else if (selected == 8) { // UTC } else if (selected == 8) { // Brazil
strncpy(config.device.tzdef, "UTC", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "BRT3", sizeof(config.device.tzdef));
} else if (selected == 9) { // EU/Western } else if (selected == 9) { // UTC
strncpy(config.device.tzdef, "UTC0", sizeof(config.device.tzdef));
} else if (selected == 10) { // EU/Western
strncpy(config.device.tzdef, "GMT0BST,M3.5.0/1,M10.5.0", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "GMT0BST,M3.5.0/1,M10.5.0", sizeof(config.device.tzdef));
} else if (selected == 10) { // EU/Central } else if (selected == 11) { // EU/Central
strncpy(config.device.tzdef, "CET-1CEST,M3.5.0,M10.5.0/3", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "CET-1CEST,M3.5.0,M10.5.0/3", sizeof(config.device.tzdef));
} else if (selected == 11) { // EU/Eastern } else if (selected == 12) { // EU/Eastern
strncpy(config.device.tzdef, "EET-2EEST,M3.5.0/3,M10.5.0/4", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "EET-2EEST,M3.5.0/3,M10.5.0/4", sizeof(config.device.tzdef));
} else if (selected == 12) { // Asia/Kolkata } else if (selected == 13) { // Asia/Kolkata
strncpy(config.device.tzdef, "IST-5:30", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "IST-5:30", sizeof(config.device.tzdef));
} else if (selected == 13) { // China } else if (selected == 14) { // China
strncpy(config.device.tzdef, "HKT-8", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "HKT-8", sizeof(config.device.tzdef));
} else if (selected == 14) { // AU/AWST } else if (selected == 15) { // AU/AWST
strncpy(config.device.tzdef, "AWST-8", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "AWST-8", sizeof(config.device.tzdef));
} else if (selected == 15) { // AU/ACST } else if (selected == 16) { // AU/ACST
strncpy(config.device.tzdef, "ACST-9:30ACDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "ACST-9:30ACDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef));
} else if (selected == 16) { // AU/AEST } else if (selected == 17) { // AU/AEST
strncpy(config.device.tzdef, "AEST-10AEDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "AEST-10AEDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef));
} else if (selected == 17) { // NZ } else if (selected == 18) { // NZ
strncpy(config.device.tzdef, "NZST-12NZDT,M9.5.0,M4.1.0/3", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, "NZST-12NZDT,M9.5.0,M4.1.0/3", sizeof(config.device.tzdef));
} }
if (selected != 0) { if (selected != 0) {
@@ -236,27 +283,25 @@ void menuHandler::clockMenu()
void menuHandler::messageResponseMenu() void menuHandler::messageResponseMenu()
{ {
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
int options = 3;
static const char **optionsArrayPtr;
int options;
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3 };
if (kb_found) { if (kb_found) {
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext"}; optionsArray[options] = "Reply via Freetext";
optionsArrayPtr = optionsArray; optionsEnumArray[options++] = Freetext;
options = 4;
} else {
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset"};
optionsArrayPtr = optionsArray;
options = 3;
} }
#ifdef HAS_I2S #ifdef HAS_I2S
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext", "Read Aloud"}; optionsArray[options] = "Read Aloud";
optionsArrayPtr = optionsArray; optionsEnumArray[options++] = Aloud;
options = 5;
#endif #endif
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action"; bannerOptions.message = "Message Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr; bannerOptions.optionsArrayPtr = optionsArray;
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) {
@@ -275,7 +320,7 @@ void menuHandler::messageResponseMenu()
} }
} }
#ifdef HAS_I2S #ifdef HAS_I2S
else if (selected == 4) { else if (selected == Aloud) {
const meshtastic_MeshPacket &mp = devicestate.rx_text_message; const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes); const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes);
@@ -288,13 +333,13 @@ void menuHandler::messageResponseMenu()
void menuHandler::homeBaseMenu() void menuHandler::homeBaseMenu()
{ {
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep }; enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Sleep, enumEnd };
static const char *optionsArray[6] = {"Back"}; static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[6] = {Back}; static int optionsEnumArray[enumEnd] = {Back};
int options = 1; int options = 1;
#ifdef PIN_EINK_EN #if defined(PIN_EINK_EN) || defined(PCA_PIN_EINK_EN)
optionsArray[options] = "Toggle Backlight"; optionsArray[options] = "Toggle Backlight";
optionsEnumArray[options++] = Backlight; optionsEnumArray[options++] = Backlight;
#else #else
@@ -310,8 +355,6 @@ void menuHandler::homeBaseMenu()
optionsArray[options] = "New Freetext Msg"; optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext; optionsEnumArray[options++] = Freetext;
} }
optionsArray[options] = "Bluetooth Toggle";
optionsEnumArray[options++] = Bluetooth;
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Home Action"; bannerOptions.message = "Home Action";
@@ -320,12 +363,24 @@ void menuHandler::homeBaseMenu()
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Backlight) { if (selected == Backlight) {
#ifdef PIN_EINK_EN #if defined(PIN_EINK_EN)
if (digitalRead(PIN_EINK_EN) == HIGH) { if (uiconfig.screen_brightness == 1) {
uiconfig.screen_brightness = 0;
digitalWrite(PIN_EINK_EN, LOW); digitalWrite(PIN_EINK_EN, LOW);
} else { } else {
uiconfig.screen_brightness = 1;
digitalWrite(PIN_EINK_EN, HIGH); digitalWrite(PIN_EINK_EN, HIGH);
} }
saveUIConfig();
#elif defined(PCA_PIN_EINK_EN)
if (uiconfig.screen_brightness == 1) {
uiconfig.screen_brightness = 0;
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
} else {
uiconfig.screen_brightness = 1;
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
}
saveUIConfig();
#endif #endif
} else if (selected == Sleep) { } else if (selected == Sleep) {
screen->setOn(false); screen->setOn(false);
@@ -336,9 +391,35 @@ void menuHandler::homeBaseMenu()
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST); cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else if (selected == Freetext) { } else if (selected == Freetext) {
cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST); cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST);
} else if (selected == Bluetooth) { }
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0}; };
inputBroker->injectInputEvent(&event); screen->showOverlayBanner(bannerOptions);
}
void menuHandler::textMessageBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "New Preset Msg";
optionsEnumArray[options++] = Preset;
if (kb_found) {
optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext;
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Preset) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else if (selected == Freetext) {
cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST);
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@@ -346,37 +427,25 @@ void menuHandler::homeBaseMenu()
void menuHandler::systemBaseMenu() void menuHandler::systemBaseMenu()
{ {
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
// Check if brightness is supported static const char *optionsArray[enumEnd] = {"Back"};
bool hasSupportBrightness = false; static int optionsEnumArray[enumEnd] = {Back};
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || HAS_TFT
hasSupportBrightness = true;
#endif
enum optionsNumbers { Back, Beeps, Brightness, Reboot, Color, MUI, Test };
static const char *optionsArray[7] = {"Back"};
static int optionsEnumArray[7] = {Back};
int options = 1; int options = 1;
optionsArray[options] = "Beeps Action"; optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Beeps; optionsEnumArray[options++] = Notifications;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \
if (hasSupportBrightness) { defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Brightness"; optionsArray[options] = "Screen Options";
optionsEnumArray[options++] = Brightness; optionsEnumArray[options++] = ScreenOptions;
}
optionsArray[options] = "Reboot";
optionsEnumArray[options++] = Reboot;
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = Color;
#endif
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsEnumArray[options++] = MUI;
#endif #endif
optionsArray[options] = "Bluetooth Toggle";
optionsEnumArray[options++] = Bluetooth;
optionsArray[options] = "Reboot/Shutdown";
optionsEnumArray[options++] = PowerMenu;
if (test_enabled) { if (test_enabled) {
optionsArray[options] = "Test Menu"; optionsArray[options] = "Test Menu";
optionsEnumArray[options++] = Test; optionsEnumArray[options++] = Test;
@@ -388,24 +457,21 @@ void menuHandler::systemBaseMenu()
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Beeps) { if (selected == Notifications) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker; menuHandler::menuQueue = menuHandler::notifications_menu;
screen->runNow(); screen->runNow();
} else if (selected == Brightness) { } else if (selected == ScreenOptions) {
menuHandler::menuQueue = menuHandler::brightness_picker; menuHandler::menuQueue = menuHandler::screen_options_menu;
screen->runNow(); screen->runNow();
} else if (selected == Reboot) { } else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::reboot_menu; menuHandler::menuQueue = menuHandler::power_menu;
screen->runNow();
} else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow();
} else if (selected == Color) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow(); screen->runNow();
} else if (selected == Test) { } else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu; menuHandler::menuQueue = menuHandler::test_menu;
screen->runNow(); screen->runNow();
} else if (selected == Bluetooth) {
menuQueue = bluetooth_toggle_menu;
screen->runNow();
} else if (selected == Back && !test_enabled) { } else if (selected == Back && !test_enabled) {
test_count++; test_count++;
if (test_count > 4) { if (test_count > 4) {
@@ -418,30 +484,37 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu() void menuHandler::favoriteBaseMenu()
{ {
int options; enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd };
static const char **optionsArrayPtr; static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2;
if (kb_found) { if (kb_found) {
static const char *optionsArray[] = {"Back", "New Preset Msg", "New Freetext Msg", "Remove Favorite"}; optionsArray[options] = "New Freetext Msg";
optionsArrayPtr = optionsArray; optionsEnumArray[options++] = Freetext;
options = 4;
} else {
static const char *optionsArray[] = {"Back", "New Preset Msg", "Remove Favorite"};
optionsArrayPtr = optionsArray;
options = 3;
} }
optionsArray[options] = "Trace Route";
optionsEnumArray[options++] = TraceRoute;
optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Favorites Action"; bannerOptions.message = "Favorites Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == Preset) {
cannedMessageModule->LaunchWithDestination(graphics::UIRenderer::currentFavoriteNodeNum); cannedMessageModule->LaunchWithDestination(graphics::UIRenderer::currentFavoriteNodeNum);
} else if (selected == 2 && kb_found) { } else if (selected == Freetext) {
cannedMessageModule->LaunchFreetextWithDestination(graphics::UIRenderer::currentFavoriteNodeNum); cannedMessageModule->LaunchFreetextWithDestination(graphics::UIRenderer::currentFavoriteNodeNum);
} else if ((!kb_found && selected == 2) || (selected == 3 && kb_found)) { } else if (selected == Remove) {
menuHandler::menuQueue = menuHandler::remove_favorite; menuHandler::menuQueue = menuHandler::remove_favorite;
screen->runNow(); screen->runNow();
} else if (selected == TraceRoute) {
if (traceRouteModule) {
traceRouteModule->launch(graphics::UIRenderer::currentFavoriteNodeNum);
}
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@@ -449,34 +522,29 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu() void menuHandler::positionBaseMenu()
{ {
int options; enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd };
static const char **optionsArrayPtr;
static const char *optionsArray[] = {"Back", "GPS Toggle", "Compass"}; static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"};
static const char *optionsArrayCalibrate[] = {"Back", "GPS Toggle", "Compass", "Compass Calibrate"}; static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
int options = 3;
if (accelerometerThread) { if (accelerometerThread) {
optionsArrayPtr = optionsArrayCalibrate; optionsArray[options] = "Compass Calibrate";
options = 4; optionsEnumArray[options++] = CompassCalibrate;
} else {
optionsArrayPtr = optionsArray;
options = 3;
} }
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action"; bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == GPSToggle) {
#if MESHTASTIC_EXCLUDE_GPS
menuQueue = menu_none;
#else
menuQueue = gps_toggle_menu; menuQueue = gps_toggle_menu;
screen->runNow(); screen->runNow();
#endif } else if (selected == CompassMenu) {
} else if (selected == 2) {
menuQueue = compass_point_north_menu; menuQueue = compass_point_north_menu;
screen->runNow(); screen->runNow();
} else if (selected == 3) { } else if (selected == CompassCalibrate) {
accelerometerThread->calibrate(30); accelerometerThread->calibrate(30);
} }
}; };
@@ -485,18 +553,25 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu() void menuHandler::nodeListMenu()
{ {
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset NodeDB"}; enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd };
static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action"; bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3; bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == Favorite) {
menuQueue = add_favorite; menuQueue = add_favorite;
screen->runNow(); screen->runNow();
} else if (selected == 2) { } else if (selected == Verify) {
menuQueue = key_verification_init;
screen->runNow();
} else if (selected == Reset) {
menuQueue = reset_node_db_menu; menuQueue = reset_node_db_menu;
screen->runNow(); screen->runNow();
} else if (selected == TraceRoute) {
menuQueue = trace_route_menu;
screen->runNow();
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@@ -522,6 +597,7 @@ void menuHandler::resetNodeDBMenu()
void menuHandler::compassNorthMenu() void menuHandler::compassNorthMenu()
{ {
enum optionsNumbers { Back, Dynamic, Fixed, Freeze };
static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"}; static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "North Directions?"; bannerOptions.message = "North Directions?";
@@ -529,28 +605,25 @@ void menuHandler::compassNorthMenu()
bannerOptions.optionsCount = 4; bannerOptions.optionsCount = 4;
bannerOptions.InitialSelected = uiconfig.compass_mode + 1; bannerOptions.InitialSelected = uiconfig.compass_mode + 1;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == Dynamic) {
if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) { if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) {
uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC; uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, saveUIConfig();
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
} else if (selected == 2) { } else if (selected == Fixed) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) { if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING; uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, saveUIConfig();
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
} else if (selected == 3) { } else if (selected == Freeze) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) { if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING; uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, saveUIConfig();
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
} else if (selected == 0) { } else if (selected == Back) {
menuQueue = position_base_menu; menuQueue = position_base_menu;
screen->runNow(); screen->runNow();
} }
@@ -561,6 +634,7 @@ void menuHandler::compassNorthMenu()
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
void menuHandler::GPSToggleMenu() void menuHandler::GPSToggleMenu()
{ {
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle GPS"; bannerOptions.message = "Toggle GPS";
@@ -587,11 +661,28 @@ void menuHandler::GPSToggleMenu()
} }
#endif #endif
void menuHandler::BluetoothToggleMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle Bluetooth";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1 || selected == 2) {
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
}
};
bannerOptions.InitialSelected = config.bluetooth.enabled ? 1 : 2;
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::BuzzerModeMenu() void menuHandler::BuzzerModeMenu()
{ {
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"}; static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Beep Action"; bannerOptions.message = "Buzzer Mode";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4; bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
@@ -641,7 +732,7 @@ void menuHandler::BrightnessPickerMenu()
#endif #endif
// Save to device // Save to device
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); saveUIConfig();
LOG_INFO("Screen brightness set to %d", uiconfig.screen_brightness); LOG_INFO("Screen brightness set to %d", uiconfig.screen_brightness);
} }
@@ -652,13 +743,13 @@ void menuHandler::BrightnessPickerMenu()
void menuHandler::switchToMUIMenu() void menuHandler::switchToMUIMenu()
{ {
static const char *optionsArray[] = {"Yes", "No"}; static const char *optionsArray[] = {"No", "Yes"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Switch to MUI?"; bannerOptions.message = "Switch to MUI?";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2; bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) { if (selected == 1) {
config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR; config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR;
config.bluetooth.enabled = false; config.bluetooth.enabled = false;
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
@@ -677,68 +768,71 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 10; bannerOptions.optionsCount = 10;
bannerOptions.bannerCallback = [display](int selected) -> void { bannerOptions.bannerCallback = [display](int selected) -> void {
uint8_t r = 0; #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
uint8_t g = 0; uint8_t TFT_MESH_r = 0;
uint8_t b = 0; uint8_t TFT_MESH_g = 0;
uint8_t TFT_MESH_b = 0;
if (selected == 1) { if (selected == 1) {
LOG_INFO("Setting color to system default or defined variant"); LOG_INFO("Setting color to system default or defined variant");
// Given just before we set all these to zero, we will allow this to go through // Given just before we set all these to zero, we will allow this to go through
} else if (selected == 2) { } else if (selected == 2) {
LOG_INFO("Setting color to Meshtastic Green"); LOG_INFO("Setting color to Meshtastic Green");
r = 103; TFT_MESH_r = 103;
g = 234; TFT_MESH_g = 234;
b = 148; TFT_MESH_b = 148;
} else if (selected == 3) { } else if (selected == 3) {
LOG_INFO("Setting color to Yellow"); LOG_INFO("Setting color to Yellow");
r = 255; TFT_MESH_r = 255;
g = 255; TFT_MESH_g = 255;
b = 128; TFT_MESH_b = 128;
} else if (selected == 4) { } else if (selected == 4) {
LOG_INFO("Setting color to Red"); LOG_INFO("Setting color to Red");
r = 255; TFT_MESH_r = 255;
g = 64; TFT_MESH_g = 64;
b = 64; TFT_MESH_b = 64;
} else if (selected == 5) { } else if (selected == 5) {
LOG_INFO("Setting color to Orange"); LOG_INFO("Setting color to Orange");
r = 255; TFT_MESH_r = 255;
g = 160; TFT_MESH_g = 160;
b = 20; TFT_MESH_b = 20;
} else if (selected == 6) { } else if (selected == 6) {
LOG_INFO("Setting color to Purple"); LOG_INFO("Setting color to Purple");
r = 204; TFT_MESH_r = 204;
g = 153; TFT_MESH_g = 153;
b = 255; TFT_MESH_b = 255;
} else if (selected == 7) { } else if (selected == 7) {
LOG_INFO("Setting color to Teal"); LOG_INFO("Setting color to Teal");
r = 64; TFT_MESH_r = 64;
g = 224; TFT_MESH_g = 224;
b = 208; TFT_MESH_b = 208;
} else if (selected == 8) { } else if (selected == 8) {
LOG_INFO("Setting color to Pink"); LOG_INFO("Setting color to Pink");
r = 255; TFT_MESH_r = 255;
g = 105; TFT_MESH_g = 105;
b = 180; TFT_MESH_b = 180;
} else if (selected == 9) { } else if (selected == 9) {
LOG_INFO("Setting color to White"); LOG_INFO("Setting color to White");
r = 255; TFT_MESH_r = 255;
g = 255; TFT_MESH_g = 255;
b = 255; TFT_MESH_b = 255;
} else {
menuQueue = system_base_menu;
screen->runNow();
} }
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
if (selected != 0) { if (selected != 0) {
display->setColor(BLACK); display->setColor(BLACK);
display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
display->setColor(WHITE); display->setColor(WHITE);
if (r == 0 && g == 0 && b == 0) { if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
#ifdef TFT_MESH_OVERRIDE #ifdef TFT_MESH_OVERRIDE
TFT_MESH = TFT_MESH_OVERRIDE; TFT_MESH = TFT_MESH_OVERRIDE;
#else #else
TFT_MESH = COLOR565(0x67, 0xEA, 0x94); TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
#endif #endif
} else { } else {
TFT_MESH = COLOR565(r, g, b); TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
} }
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
@@ -746,13 +840,13 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
#endif #endif
screen->setFrames(graphics::Screen::FOCUS_SYSTEM); screen->setFrames(graphics::Screen::FOCUS_SYSTEM);
if (r == 0 && g == 0 && b == 0) { if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
uiconfig.screen_rgb_color = 0; uiconfig.screen_rgb_color = 0;
} else { } else {
uiconfig.screen_rgb_color = (r << 16) | (g << 8) | b; uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b;
} }
LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color); LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color);
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); saveUIConfig();
} }
#endif #endif
}; };
@@ -771,6 +865,28 @@ void menuHandler::rebootMenu()
IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0)); IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0));
nodeDB->saveToDisk(); nodeDB->saveToDisk();
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
} else {
menuQueue = power_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::shutdownMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Shutdown Device?";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else {
menuQueue = power_menu;
screen->runNow();
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@@ -778,7 +894,7 @@ void menuHandler::rebootMenu()
void menuHandler::addFavoriteMenu() void menuHandler::addFavoriteMenu()
{ {
screen->showNodePicker("Node To Favorite", 30000, [](int nodenum) -> void { screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
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);
@@ -800,13 +916,24 @@ void menuHandler::removeFavoriteMenu()
bannerOptions.optionsCount = 2; bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == 1) {
LOG_INFO("Removing %x as favorite node", graphics::UIRenderer::currentFavoriteNodeNum);
nodeDB->set_favorite(false, graphics::UIRenderer::currentFavoriteNodeNum); nodeDB->set_favorite(false, graphics::UIRenderer::currentFavoriteNodeNum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_DEFAULT);
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::traceRouteMenu()
{
screen->showNodePicker("Node to Trace", 30000, [](uint32_t nodenum) -> void {
LOG_INFO("Menu: Node picker selected node 0x%08x, traceRouteModule=%p", nodenum, traceRouteModule);
if (traceRouteModule) {
traceRouteModule->startTraceRoute(nodenum);
}
});
}
void menuHandler::testMenu() void menuHandler::testMenu()
{ {
@@ -869,6 +996,153 @@ void menuHandler::wifiToggleMenu()
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::notificationsMenu()
{
enum optionsNumbers { Back, BuzzerActions };
static const char *optionsArray[] = {"Back", "Buzzer Actions"};
static int optionsEnumArray[] = {Back, BuzzerActions};
int options = 2;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Notifications";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == BuzzerActions) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::screenOptionsMenu()
{
// Check if brightness is supported
bool hasSupportBrightness = false;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
hasSupportBrightness = true;
#endif
#if defined(T_DECK)
// TDeck Doesn't seem to support brightness at all, at least not reliably
hasSupportBrightness = false;
#endif
enum optionsNumbers { Back, Brightness, ScreenColor };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
// Only show brightness for B&W displays
if (hasSupportBrightness) {
optionsArray[options] = "Brightness";
optionsEnumArray[options++] = Brightness;
}
// Only show screen color for TFT displays
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = ScreenColor;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Screen Options";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Brightness) {
menuHandler::menuQueue = menuHandler::brightness_picker;
screen->runNow();
} else if (selected == ScreenColor) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::powerMenu()
{
enum optionsNumbers { Back, Reboot, Shutdown, MUI };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
optionsArray[options] = "Reboot";
optionsEnumArray[options++] = Reboot;
optionsArray[options] = "Shutdown";
optionsEnumArray[options++] = Shutdown;
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsEnumArray[options++] = MUI;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Reboot / Shutdown";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Reboot) {
menuHandler::menuQueue = menuHandler::reboot_menu;
screen->runNow();
} else if (selected == Shutdown) {
menuHandler::menuQueue = menuHandler::shutdown_menu;
screen->runNow();
} else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::keyVerificationInitMenu()
{
screen->showNodePicker("Node to Verify", 30000,
[](uint32_t selected) -> void { keyVerificationModule->sendInitialRequest(selected); });
}
void menuHandler::keyVerificationFinalPrompt()
{
char message[40] = {0};
memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n");
keyVerificationModule->generateVerificationCode(message + 15); // send the toPhone packet
if (screen) {
static const char *optionsArray[] = {"Reject", "Accept"};
graphics::BannerOverlayOptions options;
options.message = message;
options.durationMs = 30000;
options.optionsArrayPtr = optionsArray;
options.optionsCount = 2;
options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.bannerCallback = [=](int selected) {
if (selected == 1) {
auto remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode());
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
};
screen->showOverlayBanner(options);
}
}
void menuHandler::handleMenuSwitch(OLEDDisplay *display) void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{ {
if (menuQueue != menu_none) if (menuQueue != menu_none)
@@ -879,6 +1153,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case lora_picker: case lora_picker:
LoraRegionPicker(); LoraRegionPicker();
break; break;
case no_timeout_lora_picker:
LoraRegionPicker(0);
break;
case TZ_picker: case TZ_picker:
TZPicker(); TZPicker();
break; break;
@@ -891,6 +1168,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case clock_menu: case clock_menu:
clockMenu(); clockMenu();
break; break;
case system_base_menu:
systemBaseMenu();
break;
case position_base_menu: case position_base_menu:
positionBaseMenu(); positionBaseMenu();
break; break;
@@ -920,12 +1200,18 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case reboot_menu: case reboot_menu:
rebootMenu(); rebootMenu();
break; break;
case shutdown_menu:
shutdownMenu();
break;
case add_favorite: case add_favorite:
addFavoriteMenu(); addFavoriteMenu();
break; break;
case remove_favorite: case remove_favorite:
removeFavoriteMenu(); removeFavoriteMenu();
break; break;
case trace_route_menu:
traceRouteMenu();
break;
case test_menu: case test_menu:
testMenu(); testMenu();
break; break;
@@ -935,10 +1221,36 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case wifi_toggle_menu: case wifi_toggle_menu:
wifiToggleMenu(); wifiToggleMenu();
break; break;
case key_verification_init:
keyVerificationInitMenu();
break;
case key_verification_final_prompt:
keyVerificationFinalPrompt();
break;
case bluetooth_toggle_menu:
BluetoothToggleMenu();
break;
case notifications_menu:
notificationsMenu();
break;
case screen_options_menu:
screenOptionsMenu();
break;
case power_menu:
powerMenu();
break;
case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break;
} }
menuQueue = menu_none; menuQueue = menu_none;
} }
void menuHandler::saveUIConfig()
{
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
}
} // namespace graphics } // namespace graphics
#endif #endif

View File

@@ -1,3 +1,5 @@
#pragma once
#if HAS_SCREEN
#include "configuration.h" #include "configuration.h"
namespace graphics namespace graphics
{ {
@@ -8,6 +10,7 @@ class menuHandler
enum screenMenus { enum screenMenus {
menu_none, menu_none,
lora_picker, lora_picker,
no_timeout_lora_picker,
TZ_picker, TZ_picker,
twelve_hour_picker, twelve_hour_picker,
clock_face_picker, clock_face_picker,
@@ -21,22 +24,35 @@ class menuHandler
tftcolormenupicker, tftcolormenupicker,
brightness_picker, brightness_picker,
reboot_menu, reboot_menu,
shutdown_menu,
add_favorite, add_favorite,
remove_favorite, remove_favorite,
test_menu, test_menu,
number_test, number_test,
wifi_toggle_menu wifi_toggle_menu,
bluetooth_toggle_menu,
notifications_menu,
screen_options_menu,
power_menu,
system_base_menu,
key_verification_init,
key_verification_final_prompt,
trace_route_menu,
throttle_message,
}; };
static screenMenus menuQueue; static screenMenus menuQueue;
static void OnboardMessage();
static void LoraRegionPicker(uint32_t duration = 30000); static void LoraRegionPicker(uint32_t duration = 30000);
static void handleMenuSwitch(OLEDDisplay *display); static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
static void clockMenu(); static void clockMenu();
static void TZPicker(); static void TZPicker();
static void TwelveHourPicker(); static void TwelveHourPicker();
static void ClockFacePicker(); static void ClockFacePicker();
static void messageResponseMenu(); static void messageResponseMenu();
static void homeBaseMenu(); static void homeBaseMenu();
static void textMessageBaseMenu();
static void systemBaseMenu(); static void systemBaseMenu();
static void favoriteBaseMenu(); static void favoriteBaseMenu();
static void positionBaseMenu(); static void positionBaseMenu();
@@ -49,12 +65,24 @@ class menuHandler
static void resetNodeDBMenu(); static void resetNodeDBMenu();
static void BrightnessPickerMenu(); static void BrightnessPickerMenu();
static void rebootMenu(); static void rebootMenu();
static void shutdownMenu();
static void addFavoriteMenu(); static void addFavoriteMenu();
static void removeFavoriteMenu(); static void removeFavoriteMenu();
static void traceRouteMenu();
static void testMenu(); static void testMenu();
static void numberTest(); static void numberTest();
static void wifiBaseMenu(); static void wifiBaseMenu();
static void wifiToggleMenu(); static void wifiToggleMenu();
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
private:
static void saveUIConfig();
static void keyVerificationInitMenu();
static void keyVerificationFinalPrompt();
static void BluetoothToggleMenu();
}; };
} // namespace graphics } // namespace graphics
#endif

View File

@@ -137,7 +137,11 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
display->drawString(cursorX + 1, fontY, textChunk.c_str()); display->drawString(cursorX + 1, fontY, textChunk.c_str());
} }
display->drawString(cursorX, fontY, textChunk.c_str()); display->drawString(cursorX, fontY, textChunk.c_str());
#if defined(OLED_UA) || defined(OLED_RU)
cursorX += display->getStringWidth(textChunk.c_str(), textChunk.length(), true);
#else
cursorX += display->getStringWidth(textChunk.c_str()); cursorX += display->getStringWidth(textChunk.c_str());
#endif
i = nextControl; i = nextControl;
continue; continue;
} }
@@ -155,7 +159,12 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
display->drawString(cursorX + 1, fontY, remaining.c_str()); display->drawString(cursorX + 1, fontY, remaining.c_str());
} }
display->drawString(cursorX, fontY, remaining.c_str()); display->drawString(cursorX, fontY, remaining.c_str());
#if defined(OLED_UA) || defined(OLED_RU)
cursorX += display->getStringWidth(remaining.c_str(), remaining.length(), true);
#else
cursorX += display->getStringWidth(remaining.c_str()); cursorX += display->getStringWidth(remaining.c_str());
#endif
break; break;
} }
} }
@@ -273,7 +282,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
currentKey ^= ((size_t)mp.id << 24); currentKey ^= ((size_t)mp.id << 24);
if (cachedKey != currentKey) { if (cachedKey != currentKey) {
LOG_INFO("Message cache key is misssed cachedKey=0x%0x, currentKey=0x%x", cachedKey, currentKey); LOG_INFO("Onscreen message scroll cache key needs updating: cachedKey=0x%0x, currentKey=0x%x", cachedKey, currentKey);
// Cache miss - regenerate lines and heights // Cache miss - regenerate lines and heights
cachedLines = generateLines(display, headerStr, messageBuf, textWidth); cachedLines = generateLines(display, headerStr, messageBuf, textWidth);
@@ -374,10 +383,16 @@ std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerS
} else { } else {
word += ch; word += ch;
std::string test = line + word; std::string test = line + word;
// Keep these lines for diagnostics // Keep these lines for diagnostics
// LOG_INFO("Char: '%c' (0x%02X)", ch, (unsigned char)ch); // LOG_INFO("Char: '%c' (0x%02X)", ch, (unsigned char)ch);
// LOG_INFO("Current String: %s", test.c_str()); // LOG_INFO("Current String: %s", test.c_str());
if (display->getStringWidth(test.c_str()) > textWidth) { // Note: there are boolean comparison uint16 (getStringWidth) with int (textWidth), hope textWidth is always positive :)
#if defined(OLED_UA) || defined(OLED_RU)
uint16_t strWidth = display->getStringWidth(test.c_str(), test.length(), true);
#else
uint16_t strWidth = display->getStringWidth(test.c_str());
#endif
if (strWidth > textWidth) {
if (!line.empty()) if (!line.empty())
lines.push_back(line); lines.push_back(line);
line = word; line = word;

View File

@@ -26,7 +26,7 @@ extern bool hasUnreadMessage;
namespace graphics namespace graphics
{ {
char NotificationRenderer::inEvent = INPUT_BROKER_NONE; InputEvent NotificationRenderer::inEvent;
int8_t NotificationRenderer::curSelected = 0; int8_t NotificationRenderer::curSelected = 0;
char NotificationRenderer::alertBannerMessage[256] = {0}; char NotificationRenderer::alertBannerMessage[256] = {0};
uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever
@@ -42,7 +42,7 @@ uint32_t NotificationRenderer::currentNumber = 0;
uint32_t pow_of_10(uint32_t n) uint32_t pow_of_10(uint32_t n)
{ {
uint32_t ret = 1; uint32_t ret = 1;
for (int i = 0; i < n; i++) { for (uint32_t i = 0; i < n; i++) {
ret *= 10; ret *= 10;
} }
return ret; return ret;
@@ -72,14 +72,31 @@ void NotificationRenderer::resetBanner()
{ {
alertBannerMessage[0] = '\0'; alertBannerMessage[0] = '\0';
current_notification_type = notificationTypeEnum::none; current_notification_type = notificationTypeEnum::none;
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent.kbchar = 0;
curSelected = 0;
alertBannerOptions = 0; // last x lines are seelctable options
optionsArrayPtr = nullptr;
optionsEnumPtr = nullptr;
alertBannerCallback = NULL;
pauseBanner = false;
numDigits = 0;
currentNumber = 0;
nodeDB->pause_sort(false); nodeDB->pause_sort(false);
} }
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state) void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
{ {
if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0')
resetBanner();
if (!isOverlayBannerShowing() || pauseBanner) if (!isOverlayBannerShowing() || pauseBanner)
return; return;
switch (current_notification_type) { switch (current_notification_type) {
case notificationTypeEnum::none:
// Do nothing - no notification to display
break;
case notificationTypeEnum::text_banner: case notificationTypeEnum::text_banner:
case notificationTypeEnum::selection_picker: case notificationTypeEnum::selection_picker:
drawAlertBannerOverlay(display, state); drawAlertBannerOverlay(display, state);
@@ -112,31 +129,40 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
// modulo to extract // modulo to extract
uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1)); uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1));
// Handle input // Handle input
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (this_digit == 9) { if (this_digit == 9) {
currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1)); currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1));
} else { } else {
currentNumber += (pow_of_10(numDigits - curSelected - 1)); currentNumber += (pow_of_10(numDigits - curSelected - 1));
} }
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
if (this_digit == 0) { if (this_digit == 0) {
currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1)); currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1));
} else { } else {
currentNumber -= (pow_of_10(numDigits - curSelected - 1)); currentNumber -= (pow_of_10(numDigits - curSelected - 1));
} }
} else if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_RIGHT) { } else if (inEvent.inputEvent == INPUT_BROKER_ANYKEY) {
if (inEvent.kbchar > 47 && inEvent.kbchar < 58) { // have a digit
currentNumber -= this_digit * (pow_of_10(numDigits - curSelected - 1));
currentNumber += (inEvent.kbchar - 48) * (pow_of_10(numDigits - curSelected - 1));
curSelected++;
}
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_RIGHT) {
curSelected++; curSelected++;
} else if (inEvent == INPUT_BROKER_LEFT) { } else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
curSelected--; curSelected--;
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) { } else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner(); resetBanner();
return;
} }
if (curSelected == numDigits) { if (curSelected == static_cast<int8_t>(numDigits)) {
resetBanner();
alertBannerCallback(currentNumber); alertBannerCallback(currentNumber);
resetBanner();
return;
} }
inEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0') if (alertBannerMessage[0] == '\0')
return; return;
@@ -144,12 +170,12 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
const char *linePointers[totalLines + 1] = {0}; // this is sort of a dynamic allocation const char *linePointers[totalLines + 1] = {0}; // this is sort of a dynamic allocation
// copy the linestarts to display to the linePointers holder // copy the linestarts to display to the linePointers holder
for (int i = 0; i < lineCount; i++) { for (uint16_t i = 0; i < lineCount; i++) {
linePointers[i] = lineStarts[i]; linePointers[i] = lineStarts[i];
} }
std::string digits = " "; std::string digits = " ";
std::string arrowPointer = " "; std::string arrowPointer = " ";
for (int i = 0; i < numDigits; i++) { for (uint16_t i = 0; i < numDigits; i++) {
// Modulo minus modulo to return just the current number // Modulo minus modulo to return just the current number
digits += std::to_string((currentNumber % (pow_of_10(numDigits - i))) / (pow_of_10(numDigits - i - 1))) + " "; digits += std::to_string((currentNumber % (pow_of_10(numDigits - i))) / (pow_of_10(numDigits - i - 1))) + " ";
if (curSelected == i) { if (curSelected == i) {
@@ -190,16 +216,18 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
} }
// Handle input // Handle input
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--; curSelected--;
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++; curSelected++;
} else if (inEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
resetBanner();
alertBannerCallback(selectedNodenum); alertBannerCallback(selectedNodenum);
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
resetBanner(); resetBanner();
return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner();
return;
} }
if (curSelected == -1) if (curSelected == -1)
@@ -207,7 +235,7 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
if (curSelected == alertBannerOptions) if (curSelected == alertBannerOptions)
curSelected = 0; curSelected = 0;
inEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0') if (alertBannerMessage[0] == '\0')
return; return;
@@ -305,11 +333,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
// Handle input // Handle input
if (alertBannerOptions > 0) { if (alertBannerOptions > 0) {
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) { if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--; curSelected--;
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++; curSelected++;
} else if (inEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
if (optionsEnumPtr != nullptr) { if (optionsEnumPtr != nullptr) {
alertBannerCallback(optionsEnumPtr[curSelected]); alertBannerCallback(optionsEnumPtr[curSelected]);
optionsEnumPtr = nullptr; optionsEnumPtr = nullptr;
@@ -317,8 +345,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
alertBannerCallback(curSelected); alertBannerCallback(curSelected);
} }
resetBanner(); resetBanner();
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) { return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner(); resetBanner();
return;
} }
if (curSelected == -1) if (curSelected == -1)
@@ -326,12 +357,14 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
if (curSelected == alertBannerOptions) if (curSelected == alertBannerOptions)
curSelected = 0; curSelected = 0;
} else { } else {
if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_ALT_LONG || inEvent == INPUT_BROKER_CANCEL) { if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_ALT_LONG ||
inEvent.inputEvent == INPUT_BROKER_CANCEL) {
resetBanner(); resetBanner();
return;
} }
} }
inEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0') if (alertBannerMessage[0] == '\0')
return; return;
@@ -350,7 +383,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
uint8_t firstOptionToShow = 0; uint8_t firstOptionToShow = 0;
if (alertBannerOptions > 0) { if (alertBannerOptions > 0) {
if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) { if (visibleTotalLines - lineCount == 1) {
firstOptionToShow = curSelected;
} else if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) {
if (curSelected > alertBannerOptions - visibleTotalLines + lineCount) if (curSelected > alertBannerOptions - visibleTotalLines + lineCount)
firstOptionToShow = alertBannerOptions - visibleTotalLines + lineCount; firstOptionToShow = alertBannerOptions - visibleTotalLines + lineCount;
else else
@@ -359,6 +394,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
firstOptionToShow = 0; firstOptionToShow = 0;
} }
} }
// Useful log line for troubleshooting:
/* LOG_WARN("alertBannerOptions: %u, curSelected: %u, visibleTotalLines: %u, lineCount: %u, firstOptionToShow: %u",
alertBannerOptions, curSelected, visibleTotalLines, lineCount, firstOptionToShow); */
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) { for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
if (i == curSelected) { if (i == curSelected) {

View File

@@ -11,7 +11,8 @@ namespace graphics
class NotificationRenderer class NotificationRenderer
{ {
public: public:
static char inEvent; static InputEvent inEvent;
static char inKeypress;
static int8_t curSelected; static int8_t curSelected;
static char alertBannerMessage[256]; static char alertBannerMessage[256];
static uint32_t alertBannerUntil; // 0 is a special case meaning forever static uint32_t alertBannerUntil; // 0 is a special case meaning forever

View File

@@ -24,6 +24,23 @@ extern graphics::Screen *screen;
namespace graphics namespace graphics
{ {
NodeNum UIRenderer::currentFavoriteNodeNum = 0; NodeNum UIRenderer::currentFavoriteNodeNum = 0;
std::vector<meshtastic_NodeInfoLite *> graphics::UIRenderer::favoritedNodes;
void graphics::UIRenderer::rebuildFavoritedNodes()
{
favoritedNodes.clear();
size_t total = nodeDB->getNumMeshNodes();
for (size_t i = 0; i < total; i++) {
meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (!n || n->num == nodeDB->getNodeNum())
continue;
if (n->is_favorite)
favoritedNodes.push_back(n);
}
std::sort(favoritedNodes.begin(), favoritedNodes.end(),
[](const meshtastic_NodeInfoLite *a, const meshtastic_NodeInfoLite *b) { return a->num < b->num; });
}
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
// GeoCoord object for coordinate conversions // GeoCoord object for coordinate conversions
@@ -201,27 +218,7 @@ 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)
{ {
// --- Cache favorite nodes for the current frame only, to save computation ---
static std::vector<meshtastic_NodeInfoLite *> favoritedNodes;
static int prevFrame = -1;
// --- Only rebuild favorites list if we're on a new frame ---
if (state->currentFrame != prevFrame) {
prevFrame = state->currentFrame;
favoritedNodes.clear();
size_t total = nodeDB->getNumMeshNodes();
for (size_t i = 0; i < total; i++) {
meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
// Skip nulls and ourself
if (!n || n->num == nodeDB->getNodeNum())
continue;
if (n->is_favorite)
favoritedNodes.push_back(n);
}
// Keep a stable, consistent display order
std::sort(favoritedNodes.begin(), favoritedNodes.end(),
[](const meshtastic_NodeInfoLite *a, const meshtastic_NodeInfoLite *b) { return a->num < b->num; });
}
if (favoritedNodes.empty()) if (favoritedNodes.empty())
return; return;
@@ -657,7 +654,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
char combinedName[50]; char combinedName[50];
snprintf(combinedName, sizeof(combinedName), "%s (%s)", longNameStr.empty() ? "" : longNameStr.c_str(), shortnameble); snprintf(combinedName, sizeof(combinedName), "%s (%s)", longNameStr.empty() ? "" : longNameStr.c_str(), shortnameble);
if (SCREEN_WIDTH - (display->getStringWidth(longName) + display->getStringWidth(shortnameble)) > 10) { if (SCREEN_WIDTH - (display->getStringWidth(combinedName)) > 10) {
size_t len = strlen(combinedName); size_t len = strlen(combinedName);
if (len >= 3 && strcmp(combinedName + len - 3, " ()") == 0) { if (len >= 3 && strcmp(combinedName + len - 3, " ()") == 0) {
combinedName[len - 3] = '\0'; // Remove the last three characters combinedName[len - 3] = '\0'; // Remove the last three characters
@@ -668,7 +665,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
nameX, ((rows == 4) ? getTextPositions(display)[line++] : getTextPositions(display)[line++]) + yOffset, combinedName); nameX, ((rows == 4) ? getTextPositions(display)[line++] : getTextPositions(display)[line++]) + yOffset, combinedName);
} else { } else {
// === LongName Centered === // === LongName Centered ===
textWidth = display->getStringWidth(longName); textWidth = display->getStringWidth(longNameStr.c_str());
nameX = (SCREEN_WIDTH - textWidth) / 2; nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], longNameStr.c_str()); display->drawString(nameX, getTextPositions(display)[line++], longNameStr.c_str());

View File

@@ -61,6 +61,8 @@ class UIRenderer
static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static NodeNum currentFavoriteNodeNum; static NodeNum currentFavoriteNodeNum;
static std::vector<meshtastic_NodeInfoLite *> favoritedNodes;
static void rebuildFavoritedNodes();
// OEM screens // OEM screens
#ifdef USERPREFS_OEM_TEXT #ifdef USERPREFS_OEM_TEXT

View File

@@ -1,3 +1,5 @@
#ifdef USE_EINK
#include "EinkDisplayFonts.h" #include "EinkDisplayFonts.h"
// Created by https://oleddisplay.squix.ch/ Consider a donation // Created by https://oleddisplay.squix.ch/ Consider a donation
@@ -1182,3 +1184,5 @@ const uint8_t Monospaced_plain_30[] PROGMEM = {
0xF0, 0xFF, 0x01, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0xE0, 0x00, 0xFE, 0x03, 0x00, 0xE0, 0x80, 0x7F, 0xF0, 0xFF, 0x01, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0xE0, 0x00, 0xFE, 0x03, 0x00, 0xE0, 0x80, 0x7F,
0x00, 0x00, 0xE0, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x10 // 255 0x00, 0x00, 0xE0, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x10 // 255
}; };
#endif // USE_EINK

View File

@@ -1,6 +1,8 @@
#ifndef EINKDISPLAYFONTS_h #ifndef EINKDISPLAYFONTS_h
#define EINKDISPLAYFONTS_h #define EINKDISPLAYFONTS_h
#ifdef USE_EINK
#ifdef ARDUINO #ifdef ARDUINO
#include <Arduino.h> #include <Arduino.h>
#elif __MBED__ #elif __MBED__
@@ -11,4 +13,7 @@
* Monospaced Plain 30 * Monospaced Plain 30
*/ */
extern const uint8_t Monospaced_plain_30[] PROGMEM; extern const uint8_t Monospaced_plain_30[] PROGMEM;
#endif // USE_EINK
#endif #endif

View File

@@ -1,3 +1,5 @@
#ifdef OLED_CS
#include "OLEDDisplayFontsCS.h" #include "OLEDDisplayFontsCS.h"
// Font generated or edited with the glyphEditor // Font generated or edited with the glyphEditor
@@ -1860,4 +1862,6 @@ const uint8_t ArialMT_Plain_24_CS[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00,
0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
0x06, // 255 0x06, // 255
}; };
#endif // OLED_CS

View File

@@ -1,4 +1,5 @@
// trunk-ignore-all(clang-format): Preserve long lines // trunk-ignore-all(clang-format): Preserve long lines
#ifdef OLED_PL
#include "OLEDDisplayFontsPL.h" #include "OLEDDisplayFontsPL.h"
const uint8_t ArialMT_Plain_10_PL[] PROGMEM = { const uint8_t ArialMT_Plain_10_PL[] PROGMEM = {
@@ -1310,4 +1311,6 @@ const uint8_t ArialMT_Plain_24_PL[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0x00, 0xF0, 0x01, 0x06, 0x00, 0x80, 0x0F, 0x07, 0x80, 0x00, 0xFE, 0x03, 0xE0, 0x00, 0xFC, 0x00, 0x60, 0xC0, 0x1F, 0x00, 0x20, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 253 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0x00, 0xF0, 0x01, 0x06, 0x00, 0x80, 0x0F, 0x07, 0x80, 0x00, 0xFE, 0x03, 0xE0, 0x00, 0xFC, 0x00, 0x60, 0xC0, 0x1F, 0x00, 0x20, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 253
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x03, // 254 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x03, // 254
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 255 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 255
}; };
#endif // OLED_PL

File diff suppressed because it is too large Load Diff

View File

@@ -8,4 +8,6 @@
#endif #endif
extern const uint8_t ArialMT_Plain_10_RU[] PROGMEM; extern const uint8_t ArialMT_Plain_10_RU[] PROGMEM;
extern const uint8_t ArialMT_Plain_16_RU[] PROGMEM;
extern const uint8_t ArialMT_Plain_24_RU[] PROGMEM;
#endif #endif

View File

@@ -1,3 +1,5 @@
#ifdef OLED_UA
#include "OLEDDisplayFontsUA.h" #include "OLEDDisplayFontsUA.h"
// Font generated or edited with the glyphEditor // Font generated or edited with the glyphEditor
@@ -1920,4 +1922,6 @@ const uint8_t ArialMT_Plain_24_UA[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xC1, 0x00, 0x00, 0xF0, 0xE3, 0x00, 0x00, 0x38, 0x7B, 0x00, 0x00, 0x18, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xC1, 0x00, 0x00, 0xF0, 0xE3, 0x00, 0x00, 0x38, 0x7B, 0x00, 0x00, 0x18, 0x1A, 0x00, 0x00,
0x18, 0x0E, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x00, 0xF8, 0x18, 0x0E, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x00, 0xF8,
0xFF, // 1103 0xFF, // 1103
}; };
#endif // OLED_UA

View File

@@ -0,0 +1,84 @@
#include "./E0213A367.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void E0213A367::configScanning()
{
// "Driver output control"
// Scan gates from 0 to 249 (vertical resolution 250px)
sendCommand(0x01);
sendData(0xF9);
sendData(0x00);
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
void E0213A367::configWaveform()
{
// This command (0x37) is poorly documented
// As of July 2025, the datasheet for this display's controller IC is unavailable
// The values are supplied by Heltec, who presumably have privileged access to information from the display manufacturer
// Datasheet for the similar SSD1680 IC hints at the function of this command:
// "Spare VCOM OTP selection":
// Unclear why 0x40 is set. Sane values for related SSD1680 seem to be 0x80 or 0x00.
// Maybe value is redundant? No noticeable impact when set to 0x00.
// We'll leave it set to 0x40, following Heltec's lead, just in case.
// "Display Mode"
// Seems to specify whether a waveform stored in OTP should use display mode 1 or 2 (full refresh or differential refresh)
// Unusual that waveforms are programmed to OTP, but this meta information is not ..?
sendCommand(0x37); // "Write Register for Display Option" ?
sendData(0x40); // "Spare VCOM OTP selection" ?
sendData(0x80); // "Display Mode for WS[7:0]" ?
sendData(0x03); // "Display Mode for WS[15:8]" ?
sendData(0x0E); // "Display Mode [23:16]" ?
switch (updateType) {
case FAST:
sendCommand(0x3C); // Border waveform:
sendData(0x81); // As specified by Heltec. Actually VCOM (0x80)?. Bit 0 seems redundant here.
break;
case FULL:
default:
sendCommand(0x3C); // Border waveform:
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
break;
}
}
// Tell controller IC which operations to run
void E0213A367::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory, Display mode 1 "full refresh"
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void E0213A367::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 500); // At least 500ms for fast refresh
case FULL:
default:
return beginPolling(100, 1500); // At least 1.5 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

View File

@@ -0,0 +1,41 @@
/*
E-Ink display driver
- SSD1682
- Manufacturer: SEEKINK
- Size: 2.13 inch
- Resolution: 122px x 255px
- Flex connector marking: HINK-E0213A162-A1 (hidden, printed on reverse)
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD1682.h"
namespace NicheGraphics::Drivers
{
class E0213A367 : public SSD1682
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
E0213A367() : SSD1682(width, height, supported, 0) {}
protected:
void configScanning() override;
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

View File

@@ -0,0 +1,41 @@
#include "./SSD1682.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
SSD1682::SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX)
: SSD16XX(width, height, supported, bufferOffsetX)
{
}
// SSD1682 only accepts single-byte x and y values
// This causes an incompatibility with the default SSD16XX::configFullscreen
void SSD1682::configFullscreen()
{
// Define the boundaries of the "fullscreen" region, for the controller IC
static const uint8_t sx = bufferOffsetX; // Notice the offset
static const uint8_t sy = 0;
static const uint8_t ex = bufferRowSize + bufferOffsetX - 1; // End is "max index", not "count". Minus 1 handles this
static const uint8_t ey = height;
// Data entry mode - Left to Right, Top to Bottom
sendCommand(0x11);
sendData(0x03);
// Select controller IC memory region to display a fullscreen image
sendCommand(0x44); // Memory X start - end
sendData(sx);
sendData(ex);
sendCommand(0x45); // Memory Y start - end
sendData(sy);
sendData(ey);
// Place the cursor at the start of this memory region, ready to send image data x=0 y=0
sendCommand(0x4E); // Memory cursor X
sendData(sx);
sendCommand(0x4F); // Memory cursor y
sendData(sy);
}
#endif

View File

@@ -0,0 +1,31 @@
/*
E-Ink base class for displays based on SSD1682
SSD1682 has a few quirks. We're implementing them here in a new base class,
to avoid re-implementing them every time we need to add a new SSD1682-based display.
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class SSD1682 : public SSD16XX
{
public:
SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX = 0);
virtual void configFullscreen(); // Select memory region on controller IC
virtual void deepSleep() {} // Not usable (image memory not retained)
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

View File

@@ -223,7 +223,7 @@ void InkHUD::MenuApplet::execute(MenuItem item)
case SHUTDOWN: case SHUTDOWN:
LOG_INFO("Shutting down from menu"); LOG_INFO("Shutting down from menu");
power->shutdown(); shutdownAtMsec = millis();
// Menu is then sent to background via onShutdown // Menu is then sent to background via onShutdown
break; break;

View File

@@ -39,8 +39,8 @@ void InkHUD::Events::begin()
void InkHUD::Events::onButtonShort() void InkHUD::Events::onButtonShort()
{ {
// Audio feedback (via buzzer) // Audio feedback (via buzzer)
// Short low tone // Short tone
playBoop(); playChirp();
// Cancel any beeping, buzzing, blinking // Cancel any beeping, buzzing, blinking
// Some button handling suppressed if we are dismissing an external notification (see below) // Some button handling suppressed if we are dismissing an external notification (see below)
bool dismissedExt = dismissExternalNotification(); bool dismissedExt = dismissExternalNotification();
@@ -64,8 +64,8 @@ void InkHUD::Events::onButtonShort()
void InkHUD::Events::onButtonLong() void InkHUD::Events::onButtonLong()
{ {
// Audio feedback (via buzzer) // Audio feedback (via buzzer)
// Low tone, longer than playBoop // Slightly longer than playChirp
playBeep(); playBoop();
// Check which system applet wants to handle the button press (if any) // Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr; SystemApplet *consumer = nullptr;

View File

@@ -1,7 +1,6 @@
[inkhud] [inkhud]
build_src_filter = build_src_filter =
+<graphics/niche/>; Include the nicheGraphics directory +<graphics/niche/>; Include the nicheGraphics directory
+<../variants/$PIOENV>; Include nicheGraphics.h from our variant folder
build_flags = build_flags =
-D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics -D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics
-D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD (a NicheGraphics UI) -D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD (a NicheGraphics UI)

View File

@@ -53,23 +53,21 @@ bool ButtonThread::initButton(const ButtonConfig &config)
}, },
this); this);
if (config.longPress != INPUT_BROKER_NONE) { _longPress = config.longPress;
_longPress = config.longPress; userButton.attachLongPressStart(
userButton.attachLongPressStart( [](void *callerThread) -> void {
[](void *callerThread) -> void { ButtonThread *thread = (ButtonThread *)callerThread;
ButtonThread *thread = (ButtonThread *)callerThread; // if (millis() > 30000) // hold off 30s after boot
// if (millis() > 30000) // hold off 30s after boot thread->btnEvent = BUTTON_EVENT_LONG_PRESSED;
thread->btnEvent = BUTTON_EVENT_LONG_PRESSED; },
}, this);
this); userButton.attachLongPressStop(
userButton.attachLongPressStop( [](void *callerThread) -> void {
[](void *callerThread) -> void { ButtonThread *thread = (ButtonThread *)callerThread;
ButtonThread *thread = (ButtonThread *)callerThread; // if (millis() > 30000) // hold off 30s after boot
// if (millis() > 30000) // hold off 30s after boot thread->btnEvent = BUTTON_EVENT_LONG_RELEASED;
thread->btnEvent = BUTTON_EVENT_LONG_RELEASED; },
}, this);
this);
}
if (config.doublePress != INPUT_BROKER_NONE) { if (config.doublePress != INPUT_BROKER_NONE) {
_doublePress = config.doublePress; _doublePress = config.doublePress;
@@ -94,8 +92,11 @@ bool ButtonThread::initButton(const ButtonConfig &config)
if (config.shortLong != INPUT_BROKER_NONE) { if (config.shortLong != INPUT_BROKER_NONE) {
_shortLong = config.shortLong; _shortLong = config.shortLong;
} }
#ifdef USE_EINK
userButton.setDebounceMs(0);
#else
userButton.setDebounceMs(1); userButton.setDebounceMs(1);
#endif
userButton.setPressMs(_longPressTime); userButton.setPressMs(_longPressTime);
if (screen) { if (screen) {
@@ -139,8 +140,7 @@ int32_t ButtonThread::runOnce()
} }
// Progressive lead-up sound system // Progressive lead-up sound system
if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS && if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
(millis() - buttonPressStartTime) < _longLongPressTime) {
// Start the progressive sequence if not already active // Start the progressive sequence if not already active
if (!leadUpSequenceActive) { if (!leadUpSequenceActive) {
@@ -152,13 +152,14 @@ int32_t ButtonThread::runOnce()
else if ((millis() - lastLeadUpNoteTime) >= 400) { // 400ms interval between notes else if ((millis() - lastLeadUpNoteTime) >= 400) { // 400ms interval between notes
if (playNextLeadUpNote()) { if (playNextLeadUpNote()) {
lastLeadUpNoteTime = millis(); lastLeadUpNoteTime = millis();
} else {
leadUpPlayed = true;
} }
} }
} }
// Reset when button is released // Reset when button is released
if (!buttonCurrentlyPressed && buttonWasPressed) { if (!buttonCurrentlyPressed && buttonWasPressed) {
leadUpPlayed = false;
leadUpSequenceActive = false; leadUpSequenceActive = false;
resetLeadUpSequence(); resetLeadUpSequence();
} }
@@ -202,11 +203,11 @@ int32_t ButtonThread::runOnce()
break; break;
} }
if (_longPress != INPUT_BROKER_NONE) {
// Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event) // Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event)
evt.inputEvent = _longPress; evt.inputEvent = _longPress;
this->notifyObservers(&evt); this->notifyObservers(&evt);
}
// Reset combination tracking // Reset combination tracking
waitingForLongPress = false; waitingForLongPress = false;
@@ -253,14 +254,15 @@ int32_t ButtonThread::runOnce()
// may wake the board immediatedly. // may wake the board immediatedly.
case BUTTON_EVENT_LONG_RELEASED: { case BUTTON_EVENT_LONG_RELEASED: {
LOG_INFO("LONG PRESS RELEASE"); LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE && if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
(millis() - buttonPressStartTime) >= _longLongPressTime) { (millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) {
evt.inputEvent = _longLongPress; evt.inputEvent = _longLongPress;
this->notifyObservers(&evt); this->notifyObservers(&evt);
} }
// Reset combination tracking // Reset combination tracking
waitingForLongPress = false; waitingForLongPress = false;
leadUpPlayed = false;
break; break;
} }

View File

@@ -18,13 +18,13 @@ struct ButtonConfig {
uint16_t longPressTime = 500; uint16_t longPressTime = 500;
input_broker_event doublePress = INPUT_BROKER_NONE; input_broker_event doublePress = INPUT_BROKER_NONE;
input_broker_event longLongPress = INPUT_BROKER_NONE; input_broker_event longLongPress = INPUT_BROKER_NONE;
uint16_t longLongPressTime = 5000; uint16_t longLongPressTime = 3900;
input_broker_event triplePress = INPUT_BROKER_NONE; input_broker_event triplePress = INPUT_BROKER_NONE;
input_broker_event shortLong = INPUT_BROKER_NONE; input_broker_event shortLong = INPUT_BROKER_NONE;
bool touchQuirk = false; bool touchQuirk = false;
// Constructor to set required parameter // Constructor to set required parameter
ButtonConfig(uint8_t pin = 0) : pinNumber(pin) {} explicit ButtonConfig(uint8_t pin = 0) : pinNumber(pin) {}
}; };
#ifndef BUTTON_CLICK_MS #ifndef BUTTON_CLICK_MS
@@ -62,7 +62,7 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
BUTTON_EVENT_COMBO_SHORT_LONG, BUTTON_EVENT_COMBO_SHORT_LONG,
}; };
ButtonThread(const char *name); explicit ButtonThread(const char *name);
int32_t runOnce() override; int32_t runOnce() override;
OneButton userButton; OneButton userButton;
void attachButtonInterrupts(); void attachButtonInterrupts();
@@ -92,7 +92,7 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
voidFuncPtr _intRoutine = nullptr; voidFuncPtr _intRoutine = nullptr;
uint16_t _longPressTime = 500; uint16_t _longPressTime = 500;
uint16_t _longLongPressTime = 5000; uint16_t _longLongPressTime = 3900;
int _pinNum = 0; int _pinNum = 0;
bool _activeLow = true; bool _activeLow = true;
bool _touchQuirk = false; bool _touchQuirk = false;

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