Compare commits

...

280 Commits

Author SHA1 Message Date
Thomas Göttgens
45ecd139e5 Merge branch 'master' into indicator-comms 2025-07-06 14:38:50 +02:00
renovate[bot]
708978911b chore(deps): update meshtastic/device-ui digest to 8c7092c (#7238)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-05 19:26:20 -05:00
Jonathan Bennett
2a26209889 Trunk 2025-07-05 12:56:29 -05:00
Aiden Fox Ivey
1378657206 Fix documentation comments. 2025-07-05 12:50:33 -05:00
Aiden Fox Ivey
98d010761e Add constant time compare to AES-CCM
Signed-off-by: Aiden Fox Ivey <aiden@aidenfoxivey.com>
2025-07-05 12:50:24 -05:00
Ben Meadors
798b1f4d86 Add HWIDs for T1000-E in DFU mode (#7235) 2025-07-05 07:35:20 -05:00
Jonathan Bennett
29893e0c28 Don't run ble getFromRadio() unless the phone has requested a packet (#7231) 2025-07-04 14:22:59 -05:00
github-actions[bot]
1994bb3cd1 automated bumps (#7227)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-04 08:10:23 -05:00
Jonathan Bennett
f35ca812a3 Add a WiFi menu that can toggle back to Bluetooth (#7226)
* Add Kazakhstan to the BaseUI LoRa chooser

* Add a WiFi menu that can toggle back to Bluetooth
2025-07-04 05:30:56 -05:00
renovate[bot]
abbeb4874d chore(deps): update xpowerslib to v0.3.0 (#7210)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 13:12:24 +08:00
Jonathan Bennett
0f96bd7a26 Add Kazakhstan to the BaseUI LoRa chooser (#7224) 2025-07-04 10:31:09 +08:00
renovate[bot]
dfb07e8bd2 chore(deps): update meshtastic-esp32_https_server digest to 3223704 (#7225) 2025-07-03 20:16:53 -05:00
Matt Smith
ff4eed08bc Fixed --change-mode option since it was broken (#7144)
getopts can't parse double-dash options so it had to be done separately.  Also fixed where CHANGE_MODE was checked since it wasn't working either.
2025-07-03 19:41:17 -05:00
Tom Fifield
f13dc5b903 Add Kazakhstan frequencies (#7209)
As reported by @KZ1R , Kazakhstan has frequencies in use for Lora devices that are not covered by our existing band selections.

This adds
* KZ_433 433.075 - 434.775 MHz <10 mW EIRP, Low Powered Devices (LPD)
* KZ_863 863 - 868 MHz <25 mW EIRP, 500kHz channels allowed, must not be used at airfields

Legal ref provided in https://github.com/meshtastic/firmware/issues/7204 and verified.

https://www.gov.kz/memleket/entities/mdai/press/article/details/6128
 Order of the Ministry of Investments and Development of the Republic of Kazakhstan No. 34 dated January 21, 2015.
 Published on 01 July 2024 19:03 Updated on 01 July 2024

Fixes https://github.com/meshtastic/firmware/issues/7204
2025-07-03 18:34:46 -05:00
Jonathan Bennett
c1431f4f9a Disable low brightness, as this soft-bricks at least the L1 (#7223) 2025-07-03 18:34:04 -05:00
Thomas Göttgens
79db7a5208 WIP: GPS works 2025-07-04 00:24:33 +02:00
Jason P
93132fad28 Battery Layout Updates and Icons Changes (#7221)
* Testing battery states with some info lines in the drawCommonHeader

* Update logic of USB connected, update images for states

* Tweak battery layout for isHighResolution

* Hide the magic 101%

* Adjust padding for SENSECAP_INDICATOR

* Excessive logs are unnecessary as troubleshooting is done.

* Reduce excess code - simplify readability

* Restore Lightning Bolt for Charging and related alignment issues

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-07-03 17:20:51 -05:00
todd-herbert
2254d551f4 Honor custom userPrefs boot-screens in InkHUD (#7217)
* Honor custom boot screen from userPrefs.jsonc

* Meshtastic logo when powered off, userPrefs logo at boot

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-03 15:40:43 -05:00
Austin
f2d3f54824 No routers allowed! (#7220) 2025-07-03 13:10:35 -07:00
github-actions[bot]
6fa597bc5d [create-pull-request] automated change (#7216)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-03 08:17:42 -05:00
Jonathan Bennett
b02e58521d Add GPIO edge for Native Trackball/Joystick (#7212)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-03 06:53:27 -05:00
Jonathan Bennett
81828c6244 Don't set non-existent pin on e290 (#7213)
* Don't set non-existent pin on e290

* Don't twiddle imaginary pins on the e213, either
2025-07-03 14:52:55 +10:00
Jonathan Bennett
549250b91a Good bot -- make array large enough to handle all the possible options 2025-07-02 22:39:29 -05:00
Ben Meadors
409dfe22ae Fix Seeed L1 board to enable consistent PIO flashing (#7211) 2025-07-02 20:58:15 -05:00
Jonathan Bennett
a6be2e46ed 2.7 fixes w2 (#7148)
* Initial work on splitting notification renderer into components for reuse

* More progress

* Fix notification popup

* more fix, less crash

* Adjustments for OLED on keeping menus tidy, added Bluetooth Toggle to Home frame. Also widen the frame slightly if you have a scroll bar

* Small changes for EInk to not crowd elements

* Change System frame menu over to better match actions; added color picker for T114

* Fix build errors and add T190 for testing

* Logic gates are hard sometimes

* Screen Color Picker changes, defined Yellow as a Color.

* Additional colors and tuning

* Abandon std::sort in NodeDB, and associated fixes (#7175)

* Generate short name for nodes that don't have user yet

* Add reboot menu

* Sort fixes

* noop sort option to avoid infinite loop

* Refactor Overlay Banner

* Continuing work on Color Picker

* Add BaseUI menus to add and remove Favorited Nodes

* Create TFT_MESH_OVERRIDE for variants.h and defined colors

* Trigger a NodeStatus update at the end of setup() to get fresh data on display at boot.

* T114 defaults to White, Yellow is now bright Yellow

* Revert "T114 defaults to White, Yellow is now bright Yellow"

This reverts commit 8d05e17f11.

* Only show OEM text if not OLED

* Adjust OEM logo to maximize visible area

* Start plumbing in Color Picker changes

* Finished plumbing

* Fix warning

* Revert "Fix warning"

This reverts commit 2e8aecd52d.

* Fix display not fully redrawing

* T-Deck should get color too

* Emote Revamp

* Update emotes.cpp

* Poo Emote fix

* Trunk fix

* Add secret test menu and number picker

* Missed bits

* Save colors between reboots

* Save Clock Face election to protobuf

* Make reboot first, then settings

* Add padding for single line pop-ups

* Compass saving and faster menus

* Resolve build issue with Excluding GPS

* Resolve issue with memory bars on EInk

* Add brightness settings for supported screen (#7182)

* Add brightness menu.

* add loop destination selection.

* Bring back color (and sanity) to the menus!

* Trunk

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Co-authored-by: Wilson <m.tools@qq.com>
2025-07-02 20:50:49 -05:00
renovate[bot]
3fdefe8289 chore(deps): update sensirion i2c scd4x to v1.1.0 (#7207)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 10:22:14 +10:00
todd-herbert
1f85e2a02a Additional larger font for InkHUD UI (#7201)
* Add 12pt fonts

* Add fontMedium
In addition to fontSmall and fontLarge

* Set fonts in nicheGraphics.h

* Change all uses of fontLarge to fontMedium
fontLarge was previously set at 9pt. fontLarge is now 12pt, fontMedium is 9pt. (NB: fonts may be customized per-variant)

* Use fontLarge with "All Messages" and "DMs" applets

* Documentation
2025-07-02 19:18:34 -05:00
Thomas Göttgens
7289b2a972 Update src/main.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 00:15:52 +02:00
Thomas Göttgens
9356be3f8f Update src/mesh/comms/FakeI2C.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 00:15:32 +02:00
Thomas Göttgens
cce1b050c8 Update src/mesh/comms/FakeUART.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 00:15:24 +02:00
Thomas Göttgens
fd5ff679f3 WIP 2025-07-02 23:41:51 +02:00
Austin
f99ac2104c Add customizable boot logo based on resolution (#7146) 2025-07-02 11:53:12 -07:00
Thomas Göttgens
7a7ef5f0c9 Merge branch 'master' into indicator-comms 2025-07-02 17:21:23 +02:00
Austin
553fc0cb1b Renovate comment for sensirion/Sensirion I2C SCD4x (#7202) 2025-07-02 09:11:39 -05:00
Austin
f39c7ad47e mDNS: Remove HTTP/HTTPS. Advertise shortname. (#7162) 2025-07-02 07:20:40 -05:00
Razurac
e505ec847e Added option to invert screen on InkHUD (#7075)
* Added option to invert screen on InkHUD

* Rewrite to make use of existing config.display.displaymode

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-02 06:06:02 -05:00
Daniel.Cao
90e99b2bac feat: add support for RAK3312 (New RAKwireless wiscore ESP32-S3 + SX1262) (#7115)
* Add support for RAK3112 variant with necessary configurations and definitions

* Add the configuration of the LED pin

* Refactor rak3112 variant configuration: update name, remove unused files,

* Update RAK3112 configuration: refine memory settings,  adjust GPS pin definitions

* Update RAK3112 hardware vendor definition to use correct model

* configure LED pins and update module definitions

* Update USB mode configuration for RAK3112 board

* Update power and battery configuration in rak3112 variant

* Cancel the modification of mesh.pb.h

* Rename RAKwireless RAK3112 to RAK3312

---------

Co-authored-by: daniel <daniel.cao@rakwireless.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-02 06:03:42 -05:00
github-actions[bot]
d25240b33b [create-pull-request] automated change (#7199)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-02 06:03:00 -05:00
Chloe Bethel
d494c23a88 Enable telemetry and I2C sensors on STM32WL (except accelerometers) (#7008)
* Update platformio inis for stm32 platform and wio-e5 variant for enabling i2c

* Don't reference timezone functions if MESHTASTIC_EXCLUDE_TZ is defined

* Use custom pow_of_two in RadioInterface instead of floating-point pow()

* First pass: enable sensors for STM32wL

* Fix AirQualityTelemetryModule being created if the PM25AQI header is missing

* Link in power sensor libraries

* more ini tweaks

* Add =1 to EXCLUDE defines, fix indentation.

* Drop HAS_WIRE in ini, it's defined in architecture.h

* Fix build when power sensor libraries are missing

Make MAX sensor integration into Power.cpp optional based on its library header existing.
Also make NullSensor expose a voltage and current sensor, because Power calls directly into these for INA sensors.
This lets us remove all the deps for the STM32WL platform.

* Change default I2C for RAK3172 to be I2C1, not I2C2

* Respect the laws of mathematics (oops)
2025-07-02 06:01:45 -05:00
Mictronics
17f8303e01 Fix build when MESHTASTIC_EXCLUDE_GPS is defined (#7154)
* 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.

---------

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-02 05:59:43 -05:00
dylanli
aadea89202 fix bug of cant't switch between two applets side-by-side (#7195) 2025-07-02 05:49:47 -05:00
github-actions[bot]
53013e9a7e Upgrade trunk (#7151)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-07-02 18:34:51 +10:00
Tymoteusz Jankowski
30eec01f55 Fix hydra radio (#7192)
Added missing defines to variant.h
2025-07-02 13:41:56 +10:00
dylanli
cc961d7762 update seeed device battery level map (#7194) 2025-07-02 13:39:51 +10:00
github-actions[bot]
a7528d777a [create-pull-request] automated change (#7193)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-01 20:20:22 -05:00
todd-herbert
13013a272f Limited emoji support for InkHUD (#7176)
* Cram a few emoji into AdafruitGFX fonts
Values which would normally be assigned to unprintable control characters

* Another sneaky string which may contain UTF-8 chars

* Document emoji

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-01 20:18:14 -05:00
Max
3ea96bb6e1 Log TX power after limits applyng and store it in config (#7065)
* Log and save in config lora tx_power after limits applyng

* Log and save in config lora tx_power after limits applyng

* Trunk fmt

* Remove duplicate logic

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-01 08:47:42 -05:00
Tom Fifield
baf0e9c7e6 Add detection framework for multiple AirQuality sensors (#7187)
* Add detection framework for multiple AirQuality sensors

Now we have the ability to detect multiple AirQualitySensors,
follow the lead of other sensor types and create supporting methods
and objects for using this information.

Continued cherry-picking to get #4601 over the line :)

Co-Authored-By: @Coloradohusky

* Update src/main.cpp

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-01 21:27:44 +10:00
dylanli
598eebfb10 fix t1000-e battery level map (#7186) 2025-07-01 20:16:48 +10:00
Tom Fifield
5841c889ba Add detection code for SCD4X (#7185)
* Add detection code for SCD4X

This patch adds I2C detection support SCD40/SDC41 CO2 sensors.

It's a start to get #4601 over the line :)

Co-Authored-By: @Coloradohusky

* Remove SCD4X from Portduino
2025-07-01 19:34:03 +10:00
renovate[bot]
4bd416413a chore(deps): update meshtastic/device-ui digest to 4b7bf36 (#7178)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 16:04:12 -05:00
github-actions[bot]
be06a7d881 automated bumps (#7155)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-06-30 06:05:43 -05:00
Andrew Yong
26df4f8142 fix(xiao_ble): Define xiao_ble I2C pins in parent variant (fixes #7163) (#7164)
This restores the previously-defined I2C pins that got lost in the cleanup (#7024)

Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-06-30 06:05:24 -05:00
Jonathan Bennett
b6a13f1114 Add check for theoretically impossible comparison, and drop nodenum comparison (#7165) 2025-06-28 22:54:03 -05:00
Jonathan Bennett
2bcf608654 Last second fixes (#7156)
* Ditch the 30 second delay for button presses

* Only order strictly weakly

* Too many comments!

* Only sort the populated meshNodes
2025-06-28 08:19:31 -05:00
Jonathan Bennett
705515ace2 Resize meshNodes to MAX + 1 to avoid crash during sort 2025-06-27 11:46:33 -05:00
Jonathan Bennett
a97df4bb52 Sanity check incoming UDP 2025-06-27 11:22:16 -05:00
porkcube
f6743798e2 cleanup Shutting down -> Shutting Down awkwardness (#7099)
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-06-27 11:09:04 -05:00
Ben Meadors
2ea70927c8 Revert "automated bumps (#7097)"
This reverts commit 4308bbc156.
2025-06-27 11:07:58 -05:00
Jonathan Bennett
de5b55921e Extra check on UDP packets 2025-06-27 11:06:19 -05:00
Ben Meadors
2b97576b18 NRF52 BLE fixes / tweaks (#7152)
* Try-fix: Flaky NRF52 bluetooth pairing for some users

* Safe access for screen pointer
2025-06-27 06:26:34 -05:00
Jason P
29e7a71c97 2.7 Miscellaneous Fixes - Week 1 (#7102)
* Update Favorite Node Message Options to unify against other screens

* Rebuild Horizontal Battery, Resolve overlap concerns

* Update positioning on Message frame and fix drawCommonHeader overlay

* Beginnings of creating isHighResolution bool

* Fixup determineResolution()

* Implement isHighResolution in place of SCREEN_WIDTH > 128 checks

* Line Spacing bound to isHighResolution

* Analog Clock for all

* Add AM/PM to Analog Clock if isHighResolution and not TWatch

* Simple Menu Queue, and add time menu

* Fix prompt string for 12/24 hour picker

* More menu banners into functions

* Fix Action Menu on Home frame

* Correct pop-up calculation size and continue to leverage isHighResolution

* Move menu bits to MenuHandler

* Plumb in the digital/analog picker

* Correct Clock Face Picker title

* Clock picker fixes

* Migrate the rest of the menus to MenuHandler.*

* Add compass menu and needle point option

* Minor fix for compass point menu

* Correct Home menu into typical format

* Fix emoji bounce, overlap, and missing commonHeader

* Sanitize long_names and removed unused variables

* Slightly better sanitizeString variation

* Resolved apostrophe being shown as upside down question mark

* Gotta keep height and width in expected order

* Remove Second Hand for Analog Clock on EInk displays

* Fix Clock menu option decision tree

* Improvements to Eink Navigation

* Pause Banner for Eink moved to bottom

* Updated working for 12-/24-hour menu and Added US/Arizona to timezone picker

* Add Adhoc Ping and resolve error with std::string sanitized

* Hide quick toggle as option is available within Action Menu, commented out for the moment

* Remove old battery icon and option, use drawCommonHeader throughout, re-add battery to Clock frames

* fix misc build warnings. NFC

* Update Analog Clock on EInk to show more digits

* Establish Action Menu on all node list screens, add NodeDB reset (with confirmation) option

* Add Toggle Backlight for EInk Displays

* Suppress action screen Full refresh for Eink

* Adjust drawBluetoothConnectedIcon on TWatch

* Maintain clock frame when switching between Clock Faces

* Move modules beyond the clock in navigation

* addressed the conflicts, and changed target branch to 2.7-MiscFixes-Week1

* cleanup, cheers

* Add AM/PM to low resolution clock also

* Small adjustments to AM/PM replacement across various devices

* Resolve dangling pointer issues with sanitize code

* Update comments for Screen.cpp related to module load change

* Trunk runs

* Update message caching to correct aged timestamp

* Menu wording adjustments

* Time Format wording

* Use all the rows on EInk since with autohide the navigation bar

* Finalize Time Format picker word change

* Retired drawFunctionOverlay code

No longer being used

* Actually honor the points-north setting

* Trunk

* Compressed action list

* Update no-op showOverlayBanner function

* trunk

* Correct T_Watch_S3 specific line

* Autosized Action menu per screen

* Finalize Autosized Action menu per screen

* Unify Message Titles

* Reorder Timezones to match expectations

* Adjust text location for pop-ups

* Revert "Actually honor the points-north setting"

This reverts commit 20988aa4fa.

* Make NodeDB sort its internal vector when lastheard is updated. Don't sort in NodeListRenderer

* Update src/graphics/draw/NodeListRenderer.cpp

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

* Update src/mesh/NodeDB.cpp

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

* Pass by reference -- Thanks Copilot!

* Throttle sorting just a touch

* Check more carefully for own node

* Eliminate some now-unneeded sorting

* Move function after include

* Putting Modules back to position 0 and some trunk checks found

* Add Scrollbar for Action menus

* Second attempt to move modules down the navigation bar

* Continue effort of moving modules in the navigation

* Canned Messages tweak

* Replicate Function + Space through the Menu System

* Move init button parameters into config struct (#7145)

* Remove bundling of web-ui from ESP32 devices (#7143)

* Fixed triple click GPS toggle bungle

* Move init button parameters into config struct

* Reapply "Actually honor the points-north setting"

This reverts commit 42c1967e7b.

* Actually do compass pointings correctly

* Tweak to node bearings

* Menu wording tweaks

* Get the compass_north_top logic right

* Don't jump frames after setting Compass

* Get rid of the extra bearingTo functions

* Don't blink Mail on EInk Clock Screens

* Actually set lat and long

* Calibrate

* Convert Radians to Degrees

* More degree vs radians fixes

* De-duplicate draw arrow function

* Don't advertise compass calibration without an accell thread.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: csrutil <keming.cao@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-26 22:11:20 -05:00
Ben Meadors
18fbc2149d Fix iOS bluetooth crash: Ensure UINT32_MAX is not used (#7147) 2025-06-26 19:23:08 -05:00
renovate[bot]
50424d1035 chore(deps): update meshtastic/web to v2.6.4 (#7017)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-26 12:39:03 -05:00
Ben Meadors
e87c991975 Fixed triple click GPS toggle bungle 2025-06-26 11:19:54 -05:00
Ben Meadors
2ab717cebb Remove bundling of web-ui from ESP32 devices (#7143) 2025-06-26 10:57:33 -05:00
Ben Meadors
ad23c065f6 Rate limiting fix and added 2 second rate limiting to text messages (#7139)
* Rate limiting fix and added 1.5 second rate limiting to text messages

* Remove copy-pasta

* Update src/mesh/Default.h

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

* Two is more reasonable

* Two too

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-26 07:56:34 -05:00
dylanli
eeb52a1221 support seeed_wio_tracker_L1_eink (#7125)
* initial commit of eink version

* fit for ssd1682 initial test run hud

* update to solve mirroring problem

* change eink screen ic to ssd1680

* remove HINK_E0213A367

* trunk fmt

* fix wrong type

* fix some fmt
2025-06-26 06:30:45 -05:00
Manuel
8ae05f6b33 defcon tft display size definitions (#7142) 2025-06-26 05:44:51 -05:00
github-actions[bot]
f6630cd31d Upgrade trunk (#7084)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-06-26 18:31:14 +10:00
Austin
c144bd03dc MeshAdv-Mini: Correct autoconf settings (#7117) 2025-06-25 20:17:47 -05:00
Ben Meadors
7512673b09 Do not beacon Device telemetry by default anymore (#7116)
* Do not beacon Device telemetry by default anymore

* Update

* Old default interval for sensor

* Added userpref

* Addd tracker to default telemetry roles

* Let the macro do its job in router mode
2025-06-25 16:36:33 -05:00
github-actions[bot]
3870d81bf6 [create-pull-request] automated change (#7134)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2025-06-25 20:18:55 +02:00
Kongduino
a7dcf580ad Update RedirectablePrint.cpp (#7114)
Bug fix to my hexDump code. Because `log()` adds a carriage return, hexdump lines were split over 3 lines. This fixes it.
2025-06-25 19:54:57 +02:00
todd-herbert
ecfaf3a095 Canned Messages via InkHUD menu (#7096)
* Allow observers to respond to AdminMessage requests
Ground work for CannedMessage getters and setters

* Enable CannedMessage config in apps for InkHUD devices

* Migrate the InkHUD::Events AdminModule observer
Use the new AdminModule_ObserverData struct

* Bare-bones NicheGraphics util to access canned messages
Handles loading and parsing. Handle admin messages for setting and getting.

* Send canned messages via on-screen menu

* Change ThreadedMessageApplet from Observer to Module API
Allows us to intercept locally generated packets ('loopbackOK = true'), to handle outgoing canned messages.

* Fix: crash getting empty canned message string via Client API

* Move file into Utils subdir

* Move an include statement from .cpp to .h

* Limit strncpy size of dest, not source
Wasn't critical in ths specific case, but definitely a mistake.
2025-06-25 06:04:18 -05:00
Ben Meadors
91bcf072a0 Tweak interval trottling (#7113) 2025-06-24 05:27:40 -05:00
renovate[bot]
4802cef3ca chore(deps): update radiolib to v7.2.0 (#7098)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 15:56:02 +10:00
renovate[bot]
38896198f2 chore(deps): update meshtastic/device-ui digest to cdc6e5b (#7112)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 12:04:55 -05:00
Jonathan Bennett
012f88e56f Make the 4-way on the L1 work on press instead of release (#7108) 2025-06-22 20:57:39 -05:00
Jonathan Bennett
0808f5215f fix mismatch between Exclude FSM include names (#7107) 2025-06-22 18:48:16 -05:00
Jonathan Bennett
247e05bb10 Get the unphone to stop bootlooping: increase MAX_THREADS everywhere (#7106) 2025-06-22 16:59:04 -05:00
github-actions[bot]
4308bbc156 automated bumps (#7097)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-06-22 05:54:32 -05:00
Jonathan Bennett
ce1480df98 Initialize value to fix warning 2025-06-21 23:56:14 -05:00
Jonathan Bennett
0108ad7992 Don't write the config unless the setting changed 2025-06-21 23:19:41 -05:00
Jonathan Bennett
e1df4e19e5 Default to very short updownDebounce values 2025-06-21 20:47:11 -05:00
whywilson
8ba98ae873 Add a debounce time parameter and use it in the runOnce method to debounce the key. 2025-06-21 20:40:34 -05:00
whywilson
7a38368494 Optimize key event processing and add debounce logic. 2025-06-21 20:40:24 -05:00
oscgonfer
195b7cc30a Do not add variables to json if not present (#7048) 2025-06-21 06:44:07 -05:00
Jonathan Bennett
4feaec651f Unify the native display config between legacy display and MUI (#6838)
* Add missed include

* Another Warning fix

* Add another HAS_SCREEN

* Namespace fixes

* Removed depricated destination types and re-factored destination screen

* Get rid of Arduino Strings

* Clean up after Copilot

* SixthLine Def, Screen Rename

Added Sixth Line Definition Screen Rename, and Automatic Line Adjustment

* Consistency is hard - fixed "Sixth"

* System Frame Updates

Adjusted line construction to ensure we fit maximum content per screen.

* Fix up notifications

* Add a couple more ifdef HAS_SCREEN lines

* Add screen->isOverlayBannerShowing()

* Don't forget the invert!

* Adjust Nodelist Center Divider

Adjust Nodelist Center Divider

* Fix variable casting

* Fix entryText variable as empty before update to fix validation

* Altitude is int32_t

* Update PowerTelemetry to have correct data type

* Fix cppcheck warnings (#6945)

* Fix cppcheck warnings

* Adjust logic in Power.cpp for power sensor

---------

Co-authored-by: Jason P <applewiz@mac.com>

* More pixel wrangling so things line up NodeList edition

* Adjust NodeList alignments and plumb some background padding for a possible title fix

* Better alignment for banner notifications

* Move title into drawCommonHeader; initial screen tested

* Fonts make spacing items difficult

* Improved beeping booping and other buzzer based feedback (#6947)

* Improved beeping booping and other buzzer based feedback

* audible button feedback (#6949)

* Refactor

---------

Co-authored-by: todd-herbert <herbert.todd@gmail.com>

* Sandpapered the corners of the notification popup

* Finalize drawCommonHeader migration

* Update Title of Favorite Node Screens

* Update node metric alignment on LoRa screen

* Update the border for popups to separate it from background

* Update PaxcounterModule.cpp with CommonHeader

* Update WiFi screen with CommonHeader and related data reflow

* It was not, in fact, pointing up

* Fix build on wismeshtap

* T-deck trackball debounce

* Fix uptime on Device Focused page to actually detail

* Update Sys screen for new uptime, add label to Freq/Chan on LoRa

* Don't display DOP any longer, make Uptime consistent

* Revert Uptime change on Favorites, Apply to Device Focused

* Label the satelite number to avoid confusion

* Boop boop boop boop

* Correct GPS positioning and string consistency across strings for GPS

* Fix GPS text alignment

* Enable canned messages by default

* Don't wake screen on new nodes

* Cannedmessage list emote support added

* Fn+e emote picker for freetext screen

* Actually block CannedInput actions while display is shown

* Add selection menu to bannerOverlay

* Off by one

* Move to unified text layouts and spacing

* Still my Fav without an "e"

* Fully remove EVENT_NODEDB_UPDATED

* Simply LoRa screen

* Make some char pointers const to fix compilation on native targets

* Update drawCompassNorth to include radius

* Fix warning

* button thread cleanup

* Pull OneButton handling from PowerFSM and add MUI switch (#6973)

* Trunk

* Onebutton Menu Support

* Add temporary clock icon

* Add gps location to fsi

* Banner message state reset

* Cast to char to satisfy compiler

* Better fast handling of input during banner

* Fix warning

* Derp

* oops

* Update ref

* Wire buzzer_mode

* remove legacy string->print()

* Only init screen if one found

* Unsigned Char

* More buttonThread cleaning

* screen.cpp button handling cleanup

* The Great Event Rename of 2025

* Fix the Radiomaster

* Missed trackball type change

* Remove unused function

* Make ButtonThread an InputBroker

* Coffee hadn't kicked in yet

* Add clock icon for Navigation Bar

* Restore clock screen definition code - whoops

* ExternalNotifications now observe inputBroker

* Clock rework (#6992)

* Move Clock bits into ClockRenderer space

* Rework clock into all device navigation

* T-Watch Actually Builds Different

* Compile fix

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>

* Add AM/PM to Digital Clock

* Flip Seconds and AM/PM on Clock Display

* Tik-tok pixels are hard

* Fix builds on Thinknode M1

* Check for GPS and don't crash

* Don't endif til the end

* Rework the OneButton thread to be much less of a mess. (#6997)

* Rework the OneButton thread to be much less of a mess. And break lots of targets temporarily

* Update src/input/ButtonThread.h

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

* fix GPS toggle

* Send the shutdown event, not just the kbchar

* Honor the back button in a notificaiton popup

* Draw the right size box for popup with options

* Try to un-break all the things

---------

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

* 24-hour Clock Should have leading zero, but not 12-hour

* Fixup some compile errors

* Add intRoutine to ButtonThread init, to get more responsive user button back

* Add Timezone picker

* Fix Warning

* Optionally set the initial selection for the chooser popup

* Make back buttons work in canned messages

* Drop the wrapper classes

* LonPressTime now configurable

* Clock Frame can not longer be blank; just add valid time

* Back buttons everywhere!

* Key Verification confirm banner

* Make Elecrow M* top button a back button

* Add settings saves

* EInk responsiveness fixes

* Linux Input Fixes

* Add Native Trackball/Joystick support, and move UserButton to Input

* No Flight Stick Mode

* Send input event

* Add Channel Utilization to Device Focused frame

* Don't shift screens when we draw new ones

* Add showOverlayBanner arguments to no-op

* trunk

* Default Native trackball to NC

* Fix crash in simulator mode

* Add longLong button press

* Get the args right

* Adjust Bluetooth Pairing Screen to account for bottom navigation.

* Trackball everywhere, and unPhone buttons

* Remap visionmaster secondary button to TB_UP

* Kill ScanAndSelect

* trunk

* No longer need the canned messages input filter

* All Canned All the time

* Fix stm32 compile error regarding inputBroker

* Unify tft lineheights (#7033)

* Create variable line heights based upon SCREEN_HEIGHT

* Refactor textPositions into method -> getTextPositions

* Update SharedUIDisplay.h

---------

Co-authored-by: Jason P <applewiz@mac.com>

* Adjust top distance for larger displays

* Adjust icon sizes for larger displays

* Fix Paxcounter compile errors after code updates

* Pixel wrangling to make larger screens fit better

* Alert frame has precedence over banner -- for now

* Unify on ALT_BUTTON

* Align AM/PM to the digit, not the segment on larger displays

* Move some global pin defines into configuration.h

* Scaffolding for BMM150 9-axis gyro

* Alt button behavior

* Don't add the blank GPS frames without HAS_GPS

* EVENT_NODEDB_UPDATED has been retired

* Clean out LOG_WARN messages from debugging

* Add dismiss message function

* Minor buttonThread cleanup

* Add BMM150 support

* Clean up last warning from dev

* Simplify bmm150 init return logic

* Add option to reply to messages

* Add minimal menu upon selecting home screen

* Move Messages to slot 2, rename GPS to Position, move variables nearer functional usage in Screen.cpp

* Properly dismiss message

* T-Deck Trackball press is not user button

* Add select on favorite frame to launch cannedMessage DM

* Minor wording change

* Less capital letters

* Fix empty message check, time isn't reliable

* drop dead code

* Make UIRenderer a static class instead of namespace

* Fix the select on favorite

* Check if message is empty early and then 'return'

* Add kb_found, and show the option to launch freetype if appropriate

* Ignore impossible touchscreen touches

* Auto scroll fix

* Move linebreak after "from" for banners to maximize screen usage.

* Center "No messages to show" on Message frame

* Start consolidating buzzer behavior

* Fixed signed / unsigned warning

* Cast second parameter of max() to make some targets happy

* Cast kbchar to (char) to make arduino string happy

* Shorten the notice of "No messages"

* Add buzzer mode chooser

* Add regionPicker to Lora icon

* Reduce line spacing and reorder Position screen to resolve overlapping issues

* Update message titles, fix GPS icons, add Back options

* Leftover boops

* Remove chirp

* Make the region selection dismissable when a region is already set

* Add read-aloud functionality on messages w/ esp8266sam

* "Last Heard" is a better label

* tweak the beep

* 5 options

* properly tear down freetext upon cancel

* de-convelute canned messages just a bit

* Correct height of Mail icon in navigation bar

* Remove unused warning

* Consolidate time methods into TimeFormatters

* Oops

* Change LoRa Picker Cancel to Back

* Tweak selection characters on Banner

* Message render not scrolling on 5th line

* More fixes for message scrolling

* Remove the safety next on text overflow - we found that root cause

* Add pin definitions to fix compilation for obscure target

* Don't let the touchscreen send unitialized kbchar values

* Make virtual KB just a bit quicker

* No more double tap, swipe!

* Left is left, and Right is right

* Update horizontal lightning bolt design

* Move from solid to dashed separator for Message Frame

* Single emote feature fix

* Manually sort overlapping elements for now

* Freetext and clearer choices

* Fix ESP32 InkHUD builds on the unify-tft branch (#7087)

* Remove BaseUI branding

* Capitalization is fun

* Revert Meshtastic Boot Frame Changes

* Add ANZ_433 LoRa region to picker

* Update settings.json

---------

Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: todd-herbert <herbert.todd@gmail.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-21 06:36:04 -05:00
Andrew Yong
82b7cb5dd0 fix(xiao_ble): Typo preventing SX1262 init (SX126X_CS gets stuck) (#7094)
Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-06-21 06:17:46 -05:00
Austin
30bbb449db Specify branch for create-pull-request (#7090) 2025-06-20 22:59:45 -05:00
Tom Fifield
14421c3609 Add ANZ_433 Region (#7036)
As reported by @monkeypants, the MY_433 region is not legal in ANZ due to
power limits being too high. This patch introduced an ANZ_433 region to match
the requirements in Australia and New Zealand.

433.05 - 434.79 MHz, 25mW EIRP max, No duty cycle restrictions
AU Low Interference Potential https://www.acma.gov.au/licences/low-interference-potential-devices-lipd-class-licence
NZ General User Radio Licence for Short Range Devices https://gazette.govt.nz/notice/id/2022-go3100

Fixes https://github.com/meshtastic/firmware/issues/7032#issuecomment-2972013077

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-06-20 21:39:42 -05:00
Ben Meadors
2cf7e51061 Version bump the old fashion way 2025-06-20 20:55:57 -05:00
github-actions[bot]
7fd12782a1 Bump release version (#7083)
* automated bumps

* Update version.properties

* Update changelog

* Update org.meshtastic.meshtasticd.metainfo.xml

* Update bin/org.meshtastic.meshtasticd.metainfo.xml

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

---------

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Austin <vidplace7@gmail.com>
2025-06-20 19:33:31 -05:00
renovate[bot]
c914a62d93 Update meshtastic/device-ui digest to d99edaf (#7088)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-21 08:24:02 +10:00
Ben Meadors
12680ad9cd Update README.md 2025-06-19 20:35:40 -05:00
github-actions[bot]
0561f2ca4b [create-pull-request] automated change (#7082)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-06-19 18:57:18 -05:00
Andrew Yong
58743021c8 XIAO BLE cleanup (supporting changes to seeed_xiao_nrf52840_kit too) (#7024)
* chore(seeed_xiao_nrf52840_kit): Use build flag for L76K GNSS, rename variant.h ifdef

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

* feat(seeed_xiao_nrf52840_kit): Support multiple SX126x pinouts via build flags

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

* feat(seeed_xiao_nrf52840_kit): Pin D0 as user button if pin is unused

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

* feat: EBYTE E22 and NiceRF gain and SX1262 max power defines

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

* chore(xiao_ble): Move variant to DIY and extend from seeed_xiao_nrf52840_kit

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

* feat(seeed_xiao_nrf52840_kit): Pin D6, D7 as I2C SDA, SCL if pins are unused

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

---------

Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-06-19 18:51:33 -05:00
Justin E. Mann
2fb46ce5d5 Add rak12035 VB Soil Monitor Tested & Working (#6741)
* [WIP] Add RAK12035VB Soil Moisture Sensor support

Introduce the RAK12035 sensor as an environmental telemetry sensor,
including necessary calibration checks and default values. Update
relevant files to integrate the sensor into the existing telemetry system.

This hardware is not just one module, but a couple.. RAK12023 and
RAK12035 is the component stack, the RAK12023 does not seem to matter
much and allows for multiple RAK12035 devices to be used.

Co-Authored-By: @Justin-Mann

* [WIP] Add RAK12035VB Soil Moisture Sensor support

Introduce the RAK12035 sensor as an environmental telemetry sensor,
including necessary calibration checks and default values. Update
relevant files to integrate the sensor into the existing telemetry system.

This hardware is not just one module, but a couple.. RAK12023 and
RAK12035 is the component stack, the RAK12023 does not seem to matter
much and allows for multiple RAK12035 devices to be used.

Co-Authored-By: @Justin-Mann

* [WIP] Add RAK12035VB Soil Moisture Sensor support

Introduce the RAK12035 sensor as an environmental telemetry sensor,
including necessary calibration checks and default values. Update
relevant files to integrate the sensor into the existing telemetry system.

This hardware is not just one module, but a couple.. RAK12023 and
RAK12035 is the component stack, the RAK12023 does not seem to matter
much and allows for multiple RAK12035 devices to be used.

Co-Authored-By: @Justin-Mann

* [WIP] Add RAK12035VB Soil Moisture Sensor support

Introduce the RAK12035 sensor as an environmental telemetry sensor,
including necessary calibration checks and default values. Update
relevant files to integrate the sensor into the existing telemetry system.

This hardware is not just one module, but a couple.. RAK12023 and
RAK12035 is the component stack, the RAK12023 does not seem to matter
much and allows for multiple RAK12035 devices to be used.

Co-Authored-By: @Justin-Mann

* Update to 1.0.4 release of RAK12035_SoilMoisture

* cleanup

* cool

* .

* ..

* little bit of cleanup and recompile/upload/test on RAK WISBLAOCK STACK: RAK19007/RAK4631/RAK12035VB/RAK12500

looks like soil monitor is working correctly, new environmental metrics are comming thru [new protos soil_moisture, soil_temperature] and GPS is working again with the RAK 12500.

improvements could be made around the configuration of the monitor.

next steps include updating the client(s) to react to, log and display the new proto metrics for soil temp and humidity.

* . comments about current limitations and TODOs

* trunk update

* trying to autoformat..

* fix formatting attempt 2

* ..

* ...

* ...

* .

* some corrections and local build success

* correction in temp code

* grr formatting

* cleanup after a few experiments

* remove temp code to overwrite values for temp and humidity protos.. next step just update the clients to know about soil_temperature and soil_humidity protos.

* update some values in varient for rak wistap

* working out trunk formatting..

* wip
. corrections to other build variants

* .

* protobuffs?

* protobufs?

* Update protobufs ref

* Protobufs ref

* Trunk

* Update RAK12035Sensor.cpp

* Fmt

* comment changes

* dumb mistakes... resolved, actually built and tested.. all good..

* Update src/modules/Telemetry/Sensor/RAK12035Sensor.cpp

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

* Update src/modules/Telemetry/Sensor/RAK12035Sensor.cpp

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

* . proto submod

* proto

* proto

* merge master

* mabe a fix for GPS pin conflict, waiting on a new gps module to try

* merge master, attempt to fix gps (RAK12500) pin conflict with RAK12023/12035

* .

* .

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-19 18:51:03 -05:00
Marek
8be76a56c7 PacketHistory - option to track entries' aging to log (#7067)
* PacketHistory debloat RAM allocations

* Removed FLOOD_EXPIRE_TIME option. We have static buffer now.

* Remove mx_ prefix from recentPackets

* Remember no less than 100 packet not to make reflood hell

* Cleanup

* PacketHistory max no less than 100

* no less than 100 means max of 100 or a given value of course.

* Care to not do duplicate entries. Cleanups.

* Packet History - option to log aging of entries

* Update comments for PACKET_HISTORY_TRACE_AGING and VERBOSE_PACKET_HISTORY definitions

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-19 18:48:35 -05:00
Hannes Fuchs
2c206febab Fix nugget s3 lora variant issues (#7070)
* Fix serial communication for nugget s3 lora

Without setting `ARDUINO_USB_CDC_ON_BOOT=1` the serial interface on the
nugget s3 lora board does not work.

* Fix nugget s3 lora variant definitions
2025-06-19 18:48:22 -05:00
github-actions[bot]
db1eac12af Upgrade trunk (#7073)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-06-20 09:22:03 +10:00
Jeremiah K
56e67cb434 Fix position exchange throttling issue (#7079)
* Fix position exchange throttling race condition

Separate tracking of position broadcasts vs replies to fix exchange position functionality.

Previously, allocReply() would refuse to send position replies if any position packet
(broadcast or reply) was sent within the last 3 minutes. This caused the exchange
position feature to fail when a device had recently sent a position broadcast.

Changes:
- Add lastSentReply member to track position reply timestamps separately
- Update allocReply() to only throttle based on previous replies, not broadcasts
- This allows position exchange to work even after recent position broadcasts

The fix maintains the 3-minute throttling for replies to prevent spam while allowing
legitimate position exchange functionality to work properly.

* Remove unused lastSentToMesh variable

Variable was no longer used after separating reply throttling logic.
2025-06-19 18:20:20 -05:00
Matt Smith
e9d5e36738 Replace blocking delay for wifi reconnect with non-blocking to keep button/display interactivity (#6983)
* Update WiFiAPClient.cpp to replace blocking delay() with non-blocking

* Update WiFiAPClient.cpp - fix extra endif

* Update WiFiAPClient.cpp remove duplicate section

* Update WiFiAPClient.cpp

* Update trunk_annotate_pr.yml

* Update trunk_annotate_pr.yml

* Update trunk_check.yml

* Update trunk_check.yml

* Update trunk_format_pr.yml

* Update trunk_annotate_pr.yml

* Attempted to address comments, and fix my other mess.
Thanks for your patience.

* Revert "Update trunk_annotate_pr.yml"

This reverts commit 7db4ff6444.

* Last mess cleanups (hopefully)

* Undid trunk.yaml changes

* Trunk format

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-06-19 18:18:55 -05:00
Jonathan Bennett
f71fdef3fd Update HostMetrics.cpp - don't try to print the user string (#7081)
* Update HostMetrics.cpp - don't try to print the user string

* Make Trunk Happy
2025-06-19 17:05:22 -05:00
Jonathan Bennett
5e92145324 Ensure incoming hostMetrics userstring is null terminated (#7068)
* Ensure incoming hostMetrics  userstring is null terminated

* Only null terminate user_string when has_user_string is true
2025-06-18 16:41:43 -05:00
github-actions[bot]
89a4589b68 Upgrade trunk (#7060)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-06-18 06:20:10 -05:00
Marek Veselý
20991d8b53 Add recognition for SHT40 with serial number starting with 0xc8d (#7061)
* Add recognition for SHT40 with serial number starting with 0xc8d

* fix a dumb typo :/
2025-06-18 06:19:52 -05:00
Jonathan Bennett
3ab9005b2f Make sure host_metrics user_string is null terminated 2025-06-17 11:11:36 -05:00
Marek
aabc5b7cf2 PacketHistory debloat RAM allocations (#7034)
* PacketHistory debloat RAM allocations

* Removed FLOOD_EXPIRE_TIME option. We have static buffer now.

* Remove mx_ prefix from recentPackets

* Remember no less than 100 packet not to make reflood hell

* Cleanup

* PacketHistory max no less than 100

* no less than 100 means max of 100 or a given value of course.

* Care to not do duplicate entries. Cleanups.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-17 07:18:59 -05:00
dylanliacc
afcd97c154 trunk fmt 2025-06-16 16:16:14 -05:00
Dylanliacc
cbdd7eae70 fix IIC port 2025-06-16 16:15:59 -05:00
Austin
6374ffea35 Run daily packaging earlier (PPA) (#7057) 2025-06-16 07:52:20 -05:00
Nivek-domo
1a6bb97f16 Fix RCWL9620Sensor for rak11310 support (#6617)
* Update RCWL9620Sensor.cpp

test on rak11310, work very wel now

* Update RCWL9620Sensor.cpp

* Trunk

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-16 06:54:55 -05:00
github-actions[bot]
4f0b95e910 Upgrade trunk (#7053)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-06-16 06:24:26 -05:00
github-actions[bot]
a81b41cbfb automated bumps (#7050)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-06-16 06:11:10 -05:00
todd-herbert
465fe18a89 Dismiss ExternalNotification nagging on InkHUD button press (#7056)
* Expose ExternalNotification::isNagging

* Dismiss external notification on button press
2025-06-16 06:09:55 -05:00
Taha
bd0e25f3f5 Fix Critical Error #3 for LilyGo T-Echo (#6791)
* Fix Critical Error #3

* clang format
2025-06-16 13:32:28 +10:00
Austin
9861e82f0a Manual bump metainfo version (#7049) 2025-06-15 20:16:33 -05:00
Ben Meadors
fcefd592e2 Update version.properties 2025-06-15 19:27:17 -05:00
Jonathan Bennett
8a8a7cdefc cppcheck-supress to ignore intentional error 2025-06-15 16:37:19 -05:00
Ben Meadors
8f9e569825 Create FUNDING.yml 2025-06-15 07:52:38 -05:00
Ben Meadors
b0c5327585 Trunk 2025-06-15 07:40:45 -05:00
Andy Shinn
f1dd623ce9 allow overriding INA3221 channels (#7035)
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-06-15 07:39:49 -05:00
Ben Meadors
ac52edd11a Add the ability to share ignored contacts (for blacklisting problematic nodes) (#7044) 2025-06-15 07:34:03 -05:00
github-actions[bot]
66d5dde956 [create-pull-request] automated change (#7043)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-06-15 06:45:00 -05:00
github-actions[bot]
7dfbcc8f1d Upgrade trunk (#7030)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-06-15 06:16:59 -05:00
renovate[bot]
28244148a2 chore(deps): update meshtastic/device-ui digest to 301f11e (#7042)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 06:14:53 -05:00
Jonathan Bennett
e623c70bd0 More clear key warning messages. 2025-06-14 19:35:57 -05:00
todd-herbert
425f384b1f InkHUD DIY builds for ProMicro & Heltec T114 (#7039)
* DIY InkHUD variants (ProMicro & T114)

* Fix file encoding
> We’ve detected the file encoding as ISO-8859-1. When you commit changes we will transcode it to UTF-8.

* Update comment justifying trunk suppression
2025-06-15 09:39:46 +10:00
Jonathan Bennett
1557219bad More low-entropy keys, and don't issue a false warning when changing … (#7041)
* More low-entropy keys, and don't issue a false warning when changing node name

* CopyPasta Wasn't Tasty

* When the phone sets the publickey size to 0, regenerate right away
2025-06-14 17:09:22 -05:00
Jonathan Bennett
691917b956 Add config for RAK 13300 on RAK6421 (#7037) 2025-06-14 09:59:25 -05:00
Ben Meadors
cc0fbfbd21 Fixed breaking of inkhud / tft suffix convention 2025-06-13 06:59:05 -05:00
Csrutil
5d0bf03b01 add support for GAT562 Mesh Trial Tracker (#6984)
* add support for GAT562 Mesh Trial Tracker

* Hardware Model Definition for GAT562_MESH_TRIAL_TRACKER

* Added RAK4630 for led pin 2 (blue)

* Added RAK4630 for led pin 2 (blue) comment

* don't touch src/mesh/NodeDB.cpp

* set fixed baudrate for gat562_mesh_trial_tracker

* adjust the order of the HW_VENDOR defines

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-06-13 17:27:48 +10:00
Jonathan Bennett
8ff99437cb Don't include the blank hash 2025-06-12 22:56:40 -05:00
todd-herbert
ba93097bb7 Add InkHUD driver for WeAct Studio 1.54" display module (#7000)
* Strip redundant code from E-Ink driver

* Begin polling for E-Ink update completion sooner
In some cases, we might be waiting longer than we need to.

* E-Ink driver for WeAct 1.54" display
Currently identical to the popular GDEY0154D67 model. Kept separate now in case the drivers need to diverge in future.

* Put back code which sets the number of gate lines
2025-06-12 19:59:28 -05:00
todd-herbert
de098cca4c E-Ink driver for WEAct 2.13" BW (#7001) 2025-06-12 19:58:38 -05:00
Christian Crank
8faa04afdb Validate short and long names so whitespace or empty names cannot be used (#6993)
* Say issue #6867 about adding validation for long_name and short_name. Firmware should expect at least 1 non-whitespace character for both long_name and short_name. added the USERPREFS_CONFIG_DEVICE_ROLE example to userPrefs.jsonc

* Validation for user long_name and short_name implemented. No longer can use whitespace characters. Return BAD_REQUEST error responses when validation fails and warning logs when validation rejects invalid names.

* Improve whitespace validation for user names with ctype.h, ensure logging works

* Add whitespace validation to ham mode to prevent validation bypass and to match python cli command

* punctuation change

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-12 19:58:15 -05:00
github-actions[bot]
fede1b8597 Upgrade trunk (#7006)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-06-12 19:56:53 -05:00
Tom Fifield
8557bd031d Remove GPS Baudrate locking for Seeed Xiao NRF52840 Kit (#7016)
The Seeed Xiao NRF52840 Kit's default GPS is an L76K which operates
at 9600 baud, so when this variant was defined that baud rate was
specified.

However, this is a development board and it is expected that users
can attach their own devices. This includes GPS, which may operate
at a different baud rate. The current fixed baud rate prevents this,
so this patch removes that setting.

This will revert to the regular automatic probe method. This will
sucessfully detect the L76K as before (probably the same as before
since 9600 baud is the first baud rate checked), but also allow other
GPSes at other baud rates to be detected.

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

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-12 19:56:40 -05:00
Chloe Bethel
4e6418b635 Don't use assert() with side effects in a couple more places (#7009)
* Don't use assert for Lock

* Don't use assert for MQTT messages

* Split assert in getMacAddr to always run the function

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-12 19:55:35 -05:00
Jonathan Bennett
a1a5503fe9 Another known key 2025-06-12 15:18:26 -05:00
Jonathan Bennett
3b94981e56 Key erase (#7018)
* Wipe keys if low entropy

* Client Notification Payload variant

* Don't call service before it's created

* Lucky Number 14

* Catch for low-entropy keys even before region is set
2025-06-12 12:13:39 -05:00
renovate[bot]
f299447216 chore(deps): update platform-native digest to 681ee02 (#7022)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 05:55:51 -05:00
github-actions[bot]
5f0c8863fd [create-pull-request] automated change (#7019)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-11 20:18:47 -05:00
renovate[bot]
f9d17cdee0 chore(deps): update platform-native digest to 49634e9 (#7020)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 20:18:28 -05:00
Ben Meadors
68a28a177f Add elecrow panels to BIGDB_16MB 2025-06-11 16:11:32 -05:00
Ben Meadors
60ec05e536 elecrow-adv-35-tft 2025-06-11 10:54:08 -05:00
Ben Meadors
730cd388d6 Fix pio 2025-06-11 08:49:20 -05:00
Ben Meadors
6549b0477c Missed a spot 2025-06-11 06:10:34 -05:00
Ben Meadors
8304cae010 Fix issue with CI not picking up elecrow panels due to confusing env 2025-06-11 06:09:25 -05:00
Ben Meadors
0ad9758cfd Revert "chore(deps): update meshtastic/web to v2.6.4 (#6950)" (#7015)
This reverts commit 76f7207463.
2025-06-10 18:51:54 -05:00
Jonathan Bennett
e5f6804421 Add boolean to only warn a user of a duplicated key once per boot 2025-06-10 13:28:36 -05:00
Mark Trevor Birss
720add72b2 Create lora-lyra-picocalc-wio-sx1262.yaml (#7010) 2025-06-10 16:07:24 +02:00
github-actions[bot]
693b11db1d [create-pull-request] automated change (#7007)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-06-10 06:47:41 -05:00
Jonathan Bennett
4bf2dd04ae Warn users about low entropy keys (#7003)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-10 06:33:13 -05:00
Andreas 'count' Kotes
c6c2a4d4dd Improve support for Heltec Wireless Bridge (#6647)
* Use BLE_LED where present for CONNECTED/DISCONNECTED

* Use WIFI_LED where present for WiFi started/stopped (as AP) or connected/disconnected (as Station)

* improve support for Heltec Wireless Bridge

* satisfy 'trunk fmt'
2025-06-10 13:54:07 +10:00
github-actions[bot]
79b8e7b1cf Upgrade trunk (#6998)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-06-10 13:53:04 +10:00
Travis Hardiman
cf4f088337 Update URL for ThinkNode M1 (#7005) 2025-06-10 13:52:30 +10:00
Travis Hardiman
22cb20d294 Update heltec t114 URL (#7004) 2025-06-10 13:51:37 +10:00
Clément Hampaï
1eacdd0629 [Variant] nomadstar meteor pro (#6742)
* Initial support for NomadStar Meteor Pro

* Cleaned up Platformio variant comments

* Removed RTC & ETH deps.

* Removed RGB NCP5623 deps, Enabled AmbientLight by default

* Added HWID mapping

* Updated Armduino-Semihosting lib dep with archived version.

* Fixed trunk linting in AmbientLightingThread.h and hydra variant
2025-06-10 07:48:52 +10:00
renovate[bot]
67e3d57412 chore(deps): update meshtastic/device-ui digest to 1b520fc (#6991)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 05:56:48 -05:00
Manuel
7924ef87b5 enable custom driver (#6988)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-09 05:41:41 -05:00
HarukiToreda
3dec521f75 T-watch screen misalignment fix (#6996)
* T-watch screen misalignment fix

* Trunk fix
2025-06-09 05:27:52 -05:00
renovate[bot]
57a33790ed chore(deps): update meshtastic/device-ui digest to 2fd19f8 (#6982)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 04:02:47 -05:00
renovate[bot]
484af8eb9f chore(deps): update platformio/ststm32 to v19.2.0 (#6901)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 17:54:20 +10:00
Christian Crank
b8970d66a1 Addition of Device Role inside of userPrefs.jsonc (#6972)
* addition of device.role via userprefs. USERPREFS_CONFIG_DEVICE_ROLE now usable, ROUTER*, LOST_AND_FOUND, and REPEATER disabled.

* Removing added IS_ONE_OF macro definition since meshUtils.h exists - thanks Ben!

* Fix clang-format issues in NodeDB.cpp utilizing Trunk
2025-06-08 16:51:37 +10:00
Mario Murphy
e78033bb85 Clean up install & update shell scripts (#6839)
Fixed quoting of the `FILENAME` variable to work when the path of the
passed argument contains a space. Also fixed syntactical issues called
out by `shellcheck` in multi-condition `if` statements.

Also normalized indentation chars (was mix of tabs & spaces) and
trailing whitespace.

Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-06-08 14:04:31 +10:00
Austin
8bd7adca47 Update Alpine to 3.22 (#6927) 2025-06-08 07:49:24 +10:00
renovate[bot]
f67aec40e8 chore(deps): update platformio/espressif32 to v6.11.0 (#6900)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 07:48:34 +10:00
github-actions[bot]
46c7d74760 Upgrade trunk (#6968)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-06-07 07:58:01 -05:00
Tom
15d2ae17f8 Add note to hydra to note that the button pin has no pull-up (#6979)
Add note to hydra to note that the button pin has no pull-up. Use an external resistor or remove the `#define`.
2025-06-07 06:55:58 -05:00
github-actions[bot]
91579c4650 [create-pull-request] automated change (#6980)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-06-07 06:55:25 -05:00
Andrew Yong
79b710a108 fix: Respect LED_STATE_ON for power and user LED (#6976)
Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-06-07 06:44:54 -05:00
todd-herbert
ba296db701 Add InkHUD driver for WeAct Studio 2.9" display module (#6963)
* Driver for WeAct Studio 2.9" ePaper module

* Clarify that flex connector marking is not a unique id

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-06 17:35:47 +12:00
github-actions[bot]
c0e1616382 Upgrade trunk (#6948)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-06-05 10:11:43 -05:00
Thomas Göttgens
f084a8a11d Merge branch 'master' into indicator-comms 2025-06-05 14:35:04 +02:00
Andrew Yong
070deb290f seeed_xiao_nrf52840_kit improvements (#6930)
* feat: seeed_xiao_nrf52840_kit improvements
- LEDs:
  - Change RGB LED to be active low as it is common anode
  - Remove re-definition of LED_PIN
  - Use red LED to indicate flash writes
  - Use blue LED as user LED (External Notification module)
- GPIO: Re-word unused BUTTON_PIN comment
- Wire: Set I2C pins to match XIAO nRF52840 Sense's LSM6DS3TR IMU
- Battery:
  - Use charge LED to detect charging state
  - Move voltage divider boilerplate out of src/main.cpp and into initVariant()
  - Fix dependencies for above in related XIAO BLE DIY variants

Build tested variants:
- seeed_xiao_nrf52840_kit
- xiao_ble
- seeed-xiao-nrf52840-wio-sx1262

Flashed to and tested on hardware:
- seeed_xiao_nrf52840_kit

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

* chore(seeed_xiao_nrf52840_kit): Re-order generic GPIO definitions

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

* chore: Use ADC_CTRL for XIAO nRF52840

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

---------

Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-06-05 06:45:43 -05:00
renovate[bot]
76f7207463 chore(deps): update meshtastic/web to v2.6.4 (#6950)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 15:15:51 -05:00
Jonathan Bennett
55b2bbf937 Generate keys when Lora Region is set (#6951)
* Generate keys when Lora Region changes

* Nest the ifs

* Even more entropy

* Namespacing
2025-06-04 12:16:37 -05:00
github-actions[bot]
a5716cf25c automated bumps (#6944)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-06-03 07:08:46 -05:00
Kalle Lilja
4d81280ac2 Add --1200bps-reset param to device-install/update scripts (#6752)
* add change-mode support

* add change-mode support

* tab to space

* fix if check

* change param name to 1200bps-reset

* update help section

* missed one in help seciton

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-02 20:35:26 -05:00
renovate[bot]
9ce44556ce chore(deps): update meshtastic/device-ui digest to 649e095 (#6943)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 06:29:41 -05:00
github-actions[bot]
be0c7d73a3 Upgrade trunk (#6941)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-06-02 06:16:24 -05:00
renovate[bot]
d833a9ea61 chore(deps): update meshtastic/device-ui digest to 04e3a07 (#6942)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 06:16:01 -05:00
Jonathan Bennett
5cd74f4b53 Don't give LOG_INFO a null 2025-06-01 21:03:38 -05:00
renovate[bot]
284b8bcff2 chore(deps): update meshtastic/device-ui digest to 37e2fb8 (#6925)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-31 06:15:37 -05:00
github-actions[bot]
9799f10e63 Upgrade trunk (#6922)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-05-30 08:38:13 -05:00
ArgoNavi
f9d4fdbb52 Update TSL2591Sensor.cpp (#6921)
Lower gain and timing to avoid saturation in bright light
2025-05-30 06:00:20 -05:00
Austin
b6cb0b148c Fix renovate for Adafruit PCT2075 (#6919) 2025-05-30 07:33:10 +10:00
Ben Meadors
7a7166d575 Revert "Add a new screen for heltec_wireless_paper. (#6894)" (#6918)
This reverts commit ba53543354.
2025-05-29 10:17:20 -05:00
Quency-D
ba53543354 Add a new screen for heltec_wireless_paper. (#6894)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-05-29 09:10:25 -05:00
dmarman
cb9429e83e Added full support for LTR390UV readings of UV and Lux (#6872)
* Added full support for LTR390UV readings of UV and Lux

* Trunk formatting

* Added full support for LTR390UV readings of UV and Lux

* Trunk formatting

* fix library check and unnecessary bit resolution change

* fixed log info messages and removed unused dependency

* Hopefully fixes git mess

* fix variable scope and getMetrics returns

* set metrics flags bavk to false in case something wrong happens while reading LTR390UV  mode

---------

Co-authored-by: Domingo <domingo@waxman.local>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-05-29 09:09:33 -05:00
github-actions[bot]
f972b62d89 Upgrade trunk to 1.24.0 (#6915)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-05-29 09:06:52 -05:00
Ben Meadors
7849a3d291 Trunk 2025-05-29 06:35:18 -05:00
dylanli
c0c2ec195f add support for seeed wio tracker L1 (#6907)
* add support for seeed  wio tracker l1

* add support in nrf52 arch

* fix ADC problem and comments incorrect

* fix gps wakeup pin

* fix gps pin
2025-05-29 06:33:22 -05:00
renovate[bot]
e31cd0bc77 chore(deps): update meshtastic/device-ui digest to 3dfcc97 (#6912)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 06:27:36 -05:00
todd-herbert
5195815df0 Parse own short name in LogoApplet (#6913) 2025-05-29 06:21:09 -05:00
Ben Meadors
42a80d8aed Coerce user.id to always be derive from the nodenum (#6906)
* Coerce user.id to always be derive from the nodenum

* Additionally null it out on send

* Revert "Additionally null it out on send"

This reverts commit 22a2d68723.
2025-05-28 11:30:59 -05:00
github-actions[bot]
da69d88790 [create-pull-request] automated change (#6909)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-05-28 06:20:51 -05:00
Thomas Göttgens
96c18d9908 Add LINK32 (Lilygo) Board with Light+Environment sensors (#6899)
* Add LINK32 (Lilygo) Board with Light+Environment sensors
TODO: replace PRIVATE_HW with actual HWID

* Add LINK32 (Lilygo) Board with Light+Environment sensors
TODO: replace PRIVATE_HW with actual HWID

* Update to real HWID and trunk fmt
2025-05-27 18:11:32 -05:00
GUVWAF
8908805894 Don't cancel sending ReTx for relayer if we're ROUTER(_LATE)/REPEATER (#6904)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-05-27 18:10:14 -05:00
github-actions[bot]
158c88ddef [create-pull-request] automated change (#6905)
Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com>
2025-05-27 15:41:35 -05:00
github-actions[bot]
138dc89442 Upgrade trunk (#6898)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-05-27 06:19:35 -05:00
Austin
baefda213a Linux: Adjust udev rules for gpio (#6891) 2025-05-26 06:31:10 -05:00
github-actions[bot]
106dd08710 automated bumps (#6890)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-05-25 16:53:02 -05:00
todd-herbert
f223b8a55d Add missing parsing of UTF-8 chars (#6889) 2025-05-25 13:12:52 -05:00
Ben Meadors
75a49d3486 Add heap metrics to Local stats (#6887) 2025-05-25 11:08:56 -05:00
Michael Cullen
7d95b487ef Add PCT2075 Temperature Sensor (#6829)
This is an I2C temperature sensor. It is intended to be a drop-in
compatible sensor for the LM75, however it is more accurate.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-05-25 07:29:02 -05:00
Ben Meadors
2e72850d99 Fix is_unmessagable plumbing (#6886) 2025-05-25 07:24:28 -05:00
renovate[bot]
5fbdf4b6dc chore(deps): update meshtastic/device-ui digest to e63b219 (#6883)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-25 07:12:03 -05:00
github-actions[bot]
c47bdd11f9 [create-pull-request] automated change (#6885)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-05-25 06:56:21 -05:00
github-actions[bot]
d3b16c1e47 Upgrade trunk (#6843)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-05-25 06:29:00 -05:00
Andrew Yong
e29588d2e2 feat(RadioInterface): Tx power gain calculation rework (#6796)
- Rename REGULATORY_GAIN_LORA to TX_GAIN_LORA
- Move gain-based Tx power clamping from RadioInterface::applyModemConfig() to RadioInterface::limitPower()
  - User-configured Tx power now matches the Tx power out of the device connector
- Re-order [LoRa Chip]Interface.cpp limitPower() to take place before the final Tx power clamping so we clamp based on the pre-PA Tx power rather than user-configured Tx power

Tested on XIAO BLE variant.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-05-25 06:26:31 -05:00
Chiho Sin
9b69c2a9af graphics: Add GDEY0213B74 E-Ink display driver (#6879)
Implement the GDEY0213B74 driver with configuration methods for scanning, waveform, update sequence, and polling for refresh completion. This driver supports both fast and full update types for the 2.13 inch E-Ink display.

Signed-off-by: ChihoSin chihosin@icloud.com

Signed-off-by: ChihoSin chihosin@icloud.com
2025-05-25 06:08:41 -05:00
renovate[bot]
30e83d36b7 Update meshtastic/device-ui digest to 2fba9de (#6882)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-24 19:55:19 -05:00
renovate[bot]
2c9e169451 Update meshtastic/device-ui digest to 0e9bb79 (#6880)
fix bluetooth fixedPin during restart (#135)
2025-05-24 11:33:21 +02:00
Austin
067d01b832 Bosch bsec2: Switch back to official releases (#6870) 2025-05-23 22:35:13 +02:00
Thomas Göttgens
37857941bf Merge branch 'master' into indicator-comms 2025-05-23 15:53:16 +02:00
renovate[bot]
3aed7b4190 Update Adafruit PM25 AQI Sensor to v2 (#6778) 2025-05-23 15:51:26 +02:00
dylanli
c01db98819 update seeed solar node led pin (#6871) 2025-05-23 08:04:17 -05:00
Ben Meadors
beba1b4882 Added map report precision bounds (#6862)
* Added map report precision bounds

* Log warning

* Precision range should be 12-15

* Missed commit

* Update tests

* That method was renamed

* Removed now-defunct test call

* Remove defunct test
2025-05-22 20:33:46 -05:00
todd-herbert
ba1ef45024 InkHUD Extended ASCII (#6768)
* Custom AdafruitGFX fonts with extended ASCII encodings

* AppletFont handles re-encoding of UTF-8 text

* Manual parsing of text which may contain non-ASCII chars

* Display emoji reactions, even when unprintable
Important to indicate to users that a message has been received, even if meaning is unclear.

* Superstitious shrink_to_fit
I don't think these help, but they're not hurting!

* Use Windows-1252 fonts by default

* Spelling

* Tidy up nicheGraphics.h

* Documentation

* Fix inverted logic
Slipped in during a last minute renaming while tidying up to push..
2025-05-22 18:16:53 -05:00
renovate[bot]
b12e9d43be Update meshtastic/device-ui digest to 405ca49 (#6865)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-22 14:53:16 -05:00
Ben Meadors
7cd50d7044 If a contact is add from a QR, it's "verified" manually (#6858)
* If a contact is add from a QR, it's "verified" manually

* Update src/mesh/NodeDB.cpp

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-22 07:40:36 -05:00
Jonathan Bennett
41c1b29d70 Add basic handling for is_manually_validated (#6856) 2025-05-20 21:29:29 -05:00
Ben Meadors
6041357cbb Increase the debt ceiling 2025-05-20 20:31:01 -05:00
github-actions[bot]
cf3f35d566 [create-pull-request] automated change (#6857)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-05-20 19:51:03 -05:00
Jonathan Bennett
e0f878872f Hostmetrics user string (#6850) 2025-05-20 13:04:05 -05:00
renovate[bot]
c70fa0ef13 Update meshtastic/device-ui digest to c9a55f6 (#6845)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 19:10:22 +02:00
HarukiToreda
16994c8725 Fix for ICM-20948 not initializing (#6827)
* Fix for ICM-20948 not initializing

* trunk fix
2025-05-19 06:49:38 -05:00
renovate[bot]
3dd6dc0296 Update meshtastic/device-ui digest to 48e963f (#6841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-18 14:37:54 -05:00
renovate[bot]
b2d81b740f Update dorny/test-reporter action to v2.1.0 (#6833)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-18 21:11:02 +10:00
Jonathan Bennett
61e4eb12e6 Use the _init_zero macro correctly for HostMetrics (#6837) 2025-05-17 14:02:39 -05:00
github-actions[bot]
5b312ab917 [create-pull-request] automated change (#6836)
Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com>
2025-05-17 12:29:18 -05:00
github-actions[bot]
a50a94150a [create-pull-request] automated change (#6834)
Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com>
2025-05-17 10:44:02 -05:00
renovate[bot]
3398a52a34 Update meshtastic/device-ui digest to 55f7152 (#6830)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-17 06:15:44 -05:00
Jonathan Bennett
066609a718 Remove incomplete compass boot calibration (#6825)
The made it in from testing unintentionally.
2025-05-16 12:29:08 +10:00
Jonathan Bennett
1ef4caea05 Host metrics (#6817)
* Add std::string exec() function to PortduinoGlue for future work

* MVP for HostMetrics module.

* Fix compilation for other targets

* Remove extra debug calls

* Big numbers don't do well as INTs.

* Add HostMetrics to config.yaml
2025-05-15 09:23:37 -05:00
Austin
7d8f9c7f6d Stop the madness! Run as a user (not root) (#6718)
* Stop the madness! Run as a user (not root)

* Trigger fsdir migration for < 2.6.9

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-05-15 06:40:46 -05:00
github-actions[bot]
c2d5862161 automated bumps (#6820)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-05-15 06:40:07 -05:00
Tom Fifield
6bba17d463 Add suppport for Quectel L80 (#6803)
* Add suppport for Quectel L80

Another PMTK family chip, requires only a modification to the
probe code.

* Update support for L80 based on testing.
2025-05-15 19:26:41 +10:00
Ben Meadors
ef9d0d7805 Go 2025-05-14 20:22:01 -05:00
Ben Meadors
60d2cb35e0 Namespace 2025-05-14 18:55:27 -05:00
github-actions[bot]
ed6de5095e [create-pull-request] automated change (#6815)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-05-14 17:54:21 -05:00
Ben Meadors
b63b73ab84 Fix 2025-05-14 17:16:46 -05:00
Ben Meadors
3901ae8956 Default 2025-05-14 16:44:32 -05:00
Ben Meadors
7cffd9ba70 Added new map report opt-in for compliance and limit map report (and default) to one hour (#6813)
* Added new map report opt-in for compliance and limit map report (and default) to one hour

* Trunk
2025-05-14 15:31:28 -05:00
Ben Meadors
fc64bea698 Unmessagable implementation and defaults (#6811)
* Unmessagable implementation and defaults

* Router and router late are unmessagable by default

* Update src/modules/AdminModule.cpp

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-14 15:28:09 -05:00
github-actions[bot]
a51a6b8c47 [create-pull-request] automated change (#6812)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-05-14 14:41:37 -05:00
github-actions[bot]
1af4a0bdc9 Upgrade trunk (#6797)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-05-14 14:10:45 -05:00
github-actions[bot]
bc313da064 [create-pull-request] automated change (#6810)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-05-14 13:48:14 -05:00
Austin
f16402dec1 MQTT userprefs (#6802) 2025-05-14 13:39:46 -05:00
todd-herbert
feafd2bc0c Protect T-Echo's touch button against phantom presses in OLED UI (#6735)
* Guard T-Echo touch button during LoRa TX

* Guard for T-Echo only

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-05-14 06:33:51 -05:00
rcarteraz
b1955c34aa Update Seeed Solar Node (#6763)
* Update Seeed Solar Node

* Update Seeed Solar Node

* updates

* updates

* updates

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-05-14 06:32:24 -05:00
Ben Meadors
94af3bd1ab Formatting 2025-05-14 06:31:18 -05:00
Richard Zhang
d9ad2322e8 Fixes BUG #6243 Heltec Tracker (#6781)
* fix wireless tracker screen issues

* fix SHTC3 BUG

* Remove the 32K crystal oscillator option for the Wireless Paper and Vision Master series.

* Correct spelling errors

* Update src/graphics/Screen.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-05-14 06:29:05 -05:00
Ben Meadors
0a8bd1e4be Add contact admin message (for QR code) (#6806) 2025-05-14 05:33:51 -05:00
Chris Vogel
a7415791a5 device-install.sh: detect t-eth-elite as s3 device (#6767)
device-install.sh: detect t-eth-elite as s3 device
fixes https://github.com/meshtastic/firmware/issues/6754#issuecomment-2857468902
Thanks @mverch67!
2025-05-13 15:10:57 +02:00
Thomas Göttgens
cc66f7c79b Crowpanel 4.3, 5.0, 7.0 support (#6611)
* SD software SPI control
* fix notification crash;
* allow wake on touch
* don't build non-MUI variants
* use pwm buzzer
* Finalize support for Crowpanel TFT 2.4, 2.8 and 3.5
* add hardware ID for TFT panels
* Add stubs for the bigger panels. WIP!
* fix braces
* elecrow 4.3, 5.0, 7.0 support
* completed implementation 4.3, 5.0, 7.0 variants
* NodeDB default config & simplified light sleep macros
* trunk fmt
* remove flags
* removed leftovers (note: rtc gpios are only needed for deep sleep; the remove section caused issues with the elecrows)
---------
Co-authored-by: mverch67 <manuel.verch@gmx.de>
Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>
Co-authored-by: Austin <vidplace7@gmail.com>
2025-05-13 14:15:52 +02:00
github-actions[bot]
e1417cff2e [create-pull-request] automated change (#6804)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-05-13 06:59:26 -05:00
Ben Meadors
b9fcd9da23 Add some no-nonsense coercion for self-reporting node values (#6793)
* Add some no-nonsense coercion for self-reporting node values

* Update src/mesh/PhoneAPI.cpp

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-11 18:08:29 -05:00
Ben Meadors
2a06b058fd Only send nodes on want_config of 69421 (#6792) 2025-05-11 15:18:53 -05:00
Tom
62e1974d09 Add clarifying note about AHT20 also being included with AHT10 library (#6787)
* Update AHT10.h

Add clarifying note about AHT20 also being included

* Update AHT10.cpp

Add clarifying note about AHT20 also being included
2025-05-11 17:17:37 +08:00
renovate[bot]
b208e1924f chore(deps): update meshtastic/device-ui digest to 7dee10a (#6786)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-11 00:59:38 +02:00
Austin
b17bb49a63 Actions: Fix end to end tests (#6776) 2025-05-10 16:21:23 -05:00
renovate[bot]
7c9296b0f4 chore(deps): update meshtastic/device-ui digest to 027f5a5 (#6783)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-10 18:48:39 +02:00
renovate[bot]
23fe093a65 chore(config): migrate renovate config (#6784)
* chore(config): migrate config renovate.json

* Prettier: Ignore renovate

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: vidplace7 <vidplace7@gmail.com>
2025-05-10 11:49:01 -04:00
renovate[bot]
c2a38357f1 chore(deps): update meshtastic/device-ui digest to 09b1780 (#6782)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-10 14:54:58 +02:00
github-actions[bot]
3aee4bfc6b Upgrade trunk (#6758)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-05-10 19:01:03 +08:00
Ben Meadors
981ecfdb61 Add client notification before role based power saving (sleep) (#6759)
* Add client notification before role based power saving (sleep)

* Unsigned
2025-05-07 21:20:32 -05:00
Tom
ca9bf6b31a Update XIAO_NRF_KIT RXEN Pin definition (#6717)
The XIAO NRF kit comes with a hat which has a slightly different pinout than the one supplied with the XIAO S3. At the last update, the RXEN pin was not moved with the rest.
2025-05-07 21:20:15 -05:00
renovate[bot]
ff2a12d579 chore(deps): update adafruit bme280 to v2.3.0 (#6708)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-08 08:32:01 +08:00
Jonathan Bennett
3a6fc668d8 20948 compass support (#6707)
* Add __has_include blocks for sensors

* Put BMP and BME back in the right sensors

* Split environmental_base to environmental_extra, to compile the working sensor libs for Native

* Remove hard-coded checks for ARCH_PORTDUINO

* Un-clobber bmx160

* Move BusIO to environmental_extra due to Armv7 compile error

* Move to forked BusIO for the moment

* Switch to Meshtastic ICM-20948 lib for Portduino support

* Use 20948 for compass direction

* Compass is more than just RAK4631

* Cleanup for 20948 compass

* use Meshtastic branch of 20948 lib

* Check for HAS_SCREEN for showing calibration screen

* No accelerometerThread on STM32
2025-05-07 18:38:42 -05:00
renovate[bot]
b657ba1abb chore(deps): update sparkfun 9dof imu breakout icm 20948 to v1.3.2 (#6761)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-07 13:27:44 -05:00
Thomas Göttgens
d544b41ab7 Merge branch 'master' into indicator-comms 2025-03-31 11:09:44 +02:00
Thomas Göttgens
8c53ce82f2 don't build FakeUART on other platforms 2025-03-04 11:11:25 +01:00
Thomas Göttgens
7ee95f2a0c Merge branch 'indicator-comms' of github.com:meshtastic/firmware into indicator-comms 2025-03-04 10:39:35 +01:00
Thomas Göttgens
65b50babee get sensor and NMEA data from indicator. Will test tomorrow, don't merge yet. 2025-03-04 01:56:56 +01:00
Thomas Göttgens
3c30821337 get sensor and NMEA data from indicator. Will test tomorrow, don't merge yet. 2025-03-04 01:53:18 +01:00
437 changed files with 25560 additions and 7537 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
open_collective: meshtastic

View File

@@ -27,10 +27,10 @@ inputs:
description: A newline separated list of paths to store as artifacts
required: false
default: ""
include-web-ui:
description: Include the web UI in the build
required: false
default: "false"
# include-web-ui:
# description: Include the web UI in the build
# required: false
# default: "false"
arch:
description: Processor arch name
required: true
@@ -43,29 +43,29 @@ runs:
id: base
uses: ./.github/actions/setup-base
- name: Get web ui version
if: inputs.include-web-ui == 'true'
id: webver
shell: bash
run: |
echo "ver=$(cat bin/web.version)" >> $GITHUB_OUTPUT
# - name: Get web ui version
# if: inputs.include-web-ui == 'true'
# id: webver
# shell: bash
# run: |
# echo "ver=$(cat bin/web.version)" >> $GITHUB_OUTPUT
- name: Pull web ui
if: inputs.include-web-ui == 'true'
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
target: build.tar
token: ${{ inputs.github_token }}
version: tags/v${{ steps.webver.outputs.ver }}
# - name: Pull web ui
# if: inputs.include-web-ui == 'true'
# uses: dsaltares/fetch-gh-release-asset@master
# with:
# repo: meshtastic/web
# file: build.tar
# target: build.tar
# token: ${{ inputs.github_token }}
# version: tags/v${{ steps.webver.outputs.ver }}
- name: Unpack web ui
if: inputs.include-web-ui == 'true'
shell: bash
run: |
tar -xf build.tar -C data/static
rm build.tar
# - name: Unpack web ui
# if: inputs.include-web-ui == 'true'
# shell: bash
# run: |
# tar -xf build.tar -C data/static
# rm build.tar
- name: Remove debug flags for release
shell: bash

View File

@@ -1,6 +1,7 @@
## 🙏 Thank you for sending in a pull request, here's some tips to get started!
### ❌ (Please delete all these tips and replace them with your text) ❌
- Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first
to say "Hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback
is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc...
@@ -15,12 +16,12 @@
- If you do not have the affected hardware to test your code changes adequately against regressions, please indicate this, so that contributors and commnunity members can help test your changes.
- If your PR gets accepted you can request a "Contributor" role in the Meshtastic Discord
## 🤝 Attestations
- [ ] I have tested that my proposed changes behave as described.
- [ ] I have tested that my proposed changes do not cause any obvious regressions on the following devices:
- [ ] Heltec (Lora32) V3
- [ ] LilyGo T-Deck
- [ ] LilyGo T-Deck
- [ ] LilyGo T-Beam
- [ ] RAK WisBlock 4631
- [ ] Seeed Studio T-1000E tracker card

View File

@@ -33,5 +33,5 @@ jobs:
artifact-paths: |
release/*.bin
release/*.elf
include-web-ui: true
#include-web-ui: true
arch: esp32

View File

@@ -33,5 +33,5 @@ jobs:
artifact-paths: |
release/*.bin
release/*.elf
include-web-ui: true
#include-web-ui: true
arch: esp32c3

View File

@@ -33,5 +33,5 @@ jobs:
artifact-paths: |
release/*.bin
release/*.elf
include-web-ui: true
#include-web-ui: true
arch: esp32c6

View File

@@ -33,5 +33,5 @@ jobs:
artifact-paths: |
release/*.bin
release/*.elf
include-web-ui: true
#include-web-ui: true
arch: esp32s3

View File

@@ -1,7 +1,7 @@
name: Daily Packaging
on:
schedule:
- cron: 0 9 * * *
- cron: 0 2 * * *
workflow_dispatch:
push:
branches:

View File

@@ -257,7 +257,6 @@ jobs:
./device-*.sh
./device-*.bat
./littlefs-*.bin
./littlefswebui-*.bin
./bleota*bin
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30

View File

@@ -98,6 +98,7 @@ jobs:
uses: peter-evans/create-pull-request@v7
with:
base: ${{ github.event.repository.default_branch }}
branch: create-pull-request/bump-version
title: Bump release version
commit-message: automated bumps
add-paths: |

View File

@@ -143,7 +143,7 @@ jobs:
merge-multiple: true
- name: Test Report
uses: dorny/test-reporter@v2.0.0
uses: dorny/test-reporter@v2.1.0
with:
name: PlatformIO Tests
path: testreport.xml

View File

@@ -5,7 +5,10 @@ on:
- cron: 0 0 * * * # Run every day at midnight
workflow_dispatch: {}
permissions: read-all
permissions:
contents: read
actions: read
checks: write
jobs:
native-tests:
@@ -44,7 +47,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v4

View File

@@ -33,6 +33,7 @@ jobs:
- name: Create pull request
uses: peter-evans/create-pull-request@v7
with:
branch: create-pull-request/update-protobufs
title: Update protobufs and classes
add-paths: |
protobufs

5
.gitignore vendored
View File

@@ -37,4 +37,7 @@ release/
.vscode/extensions.json
/compile_commands.json
src/mesh/raspihttp/certificate.pem
src/mesh/raspihttp/private_key.pem
src/mesh/raspihttp/private_key.pem
# Ignore logo (set at build time with platformio-custom.py)
data/boot/logo.*

View File

@@ -0,0 +1 @@
renovate.json

View File

@@ -1,34 +1,34 @@
version: 0.1
cli:
version: 1.22.15
version: 1.24.0
plugins:
sources:
- id: trunk
ref: v1.6.8
ref: v1.7.1
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- renovate@40.0.6
- prettier@3.5.3
- trufflehog@3.88.27
- checkov@3.2.447
- renovate@41.17.2
- prettier@3.6.2
- trufflehog@3.89.2
- yamllint@1.37.1
- bandit@1.8.3
- terrascan@1.19.9
- trivy@0.61.1
- bandit@1.8.5
- trivy@0.64.0
- taplo@0.9.3
- ruff@0.11.8
- ruff@0.12.1
- isort@6.0.1
- markdownlint@0.44.0
- markdownlint@0.45.0
- oxipng@9.1.5
- svgo@3.3.2
- svgo@4.0.0
- actionlint@1.7.7
- flake8@7.2.0
- flake8@7.3.0
- hadolint@2.12.1-beta
- shfmt@3.6.0
- shellcheck@0.10.0
- black@25.1.0
- git-diff-check
- gitleaks@8.25.1
- gitleaks@8.27.2
- clang-format@16.0.3
ignore:
- linters: [ALL]
@@ -38,7 +38,7 @@ runtimes:
enabled:
- python@3.10.8
- go@1.21.0
- node@18.20.5
- node@22.16.0
actions:
disabled:
- trunk-announce

View File

@@ -1,4 +1,3 @@
# trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
# trunk-ignore-all(trivy/DS002): We must run as root for this container
# trunk-ignore-all(hadolint/DL3002): We must run as root for this container
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
@@ -38,6 +37,13 @@ RUN curl -L "https://github.com/meshtastic/web/releases/download/v$(cat /tmp/fir
##### PRODUCTION BUILD #############
FROM debian:bookworm-slim
LABEL org.opencontainers.image.title="Meshtastic" \
org.opencontainers.image.description="Debian Meshtastic daemon and web interface" \
org.opencontainers.image.url="https://meshtastic.org" \
org.opencontainers.image.documentation="https://meshtastic.org/docs/" \
org.opencontainers.image.authors="Meshtastic" \
org.opencontainers.image.licenses="GPL-3.0-or-later" \
org.opencontainers.image.source="https://github.com/meshtastic/firmware/"
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC
@@ -54,7 +60,7 @@ RUN apt-get update && apt-get --no-install-recommends -y install \
&& mkdir -p /etc/meshtasticd/ssl
# Fetch compiled binary from the builder
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/bin/
COPY --from=builder /tmp/web /usr/share/meshtasticd/
# Copy config templates
COPY ./bin/config.d /etc/meshtasticd/available.d
@@ -65,8 +71,8 @@ VOLUME /var/lib/meshtasticd
# Expose Meshtastic TCP API port from the host
EXPOSE 4403
# Expose Meshtastic Web UI port from the host
EXPOSE 443
EXPOSE 9443
CMD [ "sh", "-cx", "meshtasticd -d /var/lib/meshtasticd" ]
CMD [ "sh", "-cx", "meshtasticd --fsdir=/var/lib/meshtasticd" ]
HEALTHCHECK NONE

View File

@@ -37,3 +37,4 @@ Join our community and help improve Meshtastic! 🚀
## Stats
![Alt](https://repobeats.axiom.co/api/embed/8025e56c482ec63541593cc5bd322c19d5c0bdcf.svg "Repobeats analytics image")

View File

@@ -3,7 +3,7 @@
# trunk-ignore-all(hadolint/DL3018): Do not pin apk package versions
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
FROM python:3.13-alpine3.21 AS builder
FROM python:3.13-alpine3.22 AS builder
ARG PIO_ENV=native
ENV PIP_ROOT_USER_ACTION=ignore
@@ -27,13 +27,20 @@ RUN bash ./bin/build-native.sh "$PIO_ENV" && \
# ##### PRODUCTION BUILD #############
FROM alpine:3.21
FROM alpine:3.22
LABEL org.opencontainers.image.title="Meshtastic" \
org.opencontainers.image.description="Alpine Meshtastic daemon" \
org.opencontainers.image.url="https://meshtastic.org" \
org.opencontainers.image.documentation="https://meshtastic.org/docs/" \
org.opencontainers.image.authors="Meshtastic" \
org.opencontainers.image.licenses="GPL-3.0-or-later" \
org.opencontainers.image.source="https://github.com/meshtastic/firmware/"
# nosemgrep: dockerfile.security.last-user-is-root.last-user-is-root
USER root
RUN apk --no-cache add \
libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
shadow libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
libx11 libinput libxkbcommon \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /var/lib/meshtasticd \
@@ -41,7 +48,7 @@ RUN apk --no-cache add \
&& mkdir -p /etc/meshtasticd/ssl
# Fetch compiled binary from the builder
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/bin/
# Copy config templates
COPY ./bin/config.d /etc/meshtasticd/available.d

View File

@@ -4,7 +4,7 @@ extends = arduino_base
custom_esp32_kind = esp32
platform =
# renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32
platformio/espressif32@6.10.0
platformio/espressif32@6.11.0
build_src_filter =
${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp>
@@ -49,13 +49,13 @@ lib_deps =
${environmental_extra.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master
https://github.com/meshtastic/esp32_https_server/archive/896f1771ceb5979987a0b41028bf1b4e7aad419b.zip
https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
h2zero/NimBLE-Arduino@^1.4.3
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@^0.2.7
lewisxhe/XPowersLib@0.3.0
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

View File

@@ -28,7 +28,7 @@ lib_deps =
${environmental_extra.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@^0.2.7
lewisxhe/XPowersLib@0.3.0
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

View File

@@ -2,7 +2,7 @@
[portduino_base]
platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
https://github.com/meshtastic/platform-native/archive/622341c6de8a239704318b10c3dbb00c21a3eab3.zip
https://github.com/meshtastic/platform-native/archive/681ee029207e9fd040afa223df6e54074cbbe084.zip
framework = arduino
build_src_filter =

View File

@@ -2,7 +2,7 @@
extends = arduino_base
platform =
# renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32
platformio/ststm32@19.1.0
platformio/ststm32@19.2.0
platform_packages =
# TODO renovate
platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip
@@ -16,14 +16,17 @@ build_flags =
${arduino_base.build_flags}
-flto
-Isrc/platform/stm32wl -g
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
-DMESHTASTIC_EXCLUDE_INPUTBROKER
-DMESHTASTIC_EXCLUDE_I2C
-DMESHTASTIC_EXCLUDE_POWERMON
-DMESHTASTIC_EXCLUDE_SCREEN
-DMESHTASTIC_EXCLUDE_MQTT
-DMESHTASTIC_EXCLUDE_BLUETOOTH
-DMESHTASTIC_EXCLUDE_GPS
-DMESHTASTIC_EXCLUDE_AUDIO=1
-DMESHTASTIC_EXCLUDE_ATAK=1 ; ATAK is quite big, disable it for big flash savings.
-DMESHTASTIC_EXCLUDE_INPUTBROKER=1
-DMESHTASTIC_EXCLUDE_POWERMON=1
-DMESHTASTIC_EXCLUDE_SCREEN=1
-DMESHTASTIC_EXCLUDE_MQTT=1
-DMESHTASTIC_EXCLUDE_BLUETOOTH=1
-DMESHTASTIC_EXCLUDE_GPS=1
-DMESHTASTIC_EXCLUDE_WIFI=1
-DMESHTASTIC_EXCLUDE_TZ=1 ; Exclude TZ to save some flash space.
-DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF ; This is REQUIRED for at least traceroute debug prints - without it the length ends up uninitialized.
;-DDEBUG_MUTE
-fmerge-all-constants
-ffunction-sections
@@ -39,9 +42,9 @@ debug_tool = stlink
lib_deps =
${env.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip
lib_ignore =
mathertel/OneButton@2.6.1
Wire

View File

@@ -0,0 +1,7 @@
# Set spidev ownership to 'spi' group
SUBSYSTEM=="spidev", KERNEL=="spidev*", GROUP="spi", MODE="0660"
# Allow access to USB CH341 devices
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="5512", MODE="0666"
# Set gpio ownership to 'gpio' group
SUBSYSTEM=="*gpiomem*", GROUP="gpio", MODE="0660"
SUBSYSTEM=="gpio", GROUP="gpio", MODE="0660"

View File

@@ -34,11 +34,12 @@ SRCBIN=.pio/build/$1/firmware.bin
cp $SRCBIN $OUTDIR/$basename-update.bin
echo "Building Filesystem for ESP32 targets"
pio run --environment $1 -t buildfs
cp .pio/build/$1/littlefs.bin $OUTDIR/littlefswebui-$1-$VERSION.bin
# Remove webserver files from the filesystem and rebuild
ls -l data/static # Diagnostic list of files
rm -rf data/static
# If you want to build the webui, uncomment the following lines
# pio run --environment $1 -t buildfs
# cp .pio/build/$1/littlefs.bin $OUTDIR/littlefswebui-$1-$VERSION.bin
# # Remove webserver files from the filesystem and rebuild
# ls -l data/static # Diagnostic list of files
# rm -rf data/static
pio run --environment $1 -t buildfs
cp .pio/build/$1/littlefs.bin $OUTDIR/littlefs-$1-$VERSION.bin
cp bin/device-install.* $OUTDIR

View File

@@ -23,4 +23,4 @@ for BOARD in $BOARDS; do
CHECK="${CHECK} -e ${BOARD}"
done
pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt" $CHECK --skip-packages --pattern="src/" --fail-on-defect=medium --fail-on-defect=high
pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt --inline-suppr" $CHECK --skip-packages --pattern="src/" --fail-on-defect=medium --fail-on-defect=high

View File

@@ -96,9 +96,9 @@ Lora:
### Some devices, like the pinedio, may require spidev0.1 as a workaround.
# spidev: spidev0.0
### Define GPIO buttons here:
### Deprecated location for User Button:
GPIO:
#GPIO:
# User: 6
### Define GPS
@@ -115,17 +115,6 @@ I2C:
Display:
### Waveshare 1.44inch LCD HAT
# Panel: ST7735S
# CS: 8 #Chip Select
# DC: 25 # Data/Command pin
# Backlight: 24
# Width: 128
# Height: 128
# Reset: 27
# OffsetX: 0
# OffsetY: 0
### Adafruit PiTFT 2.8 TFT+Touchscreen
# Panel: ILI9341
# CS: 8
@@ -180,6 +169,16 @@ Input:
# KeyboardDevice: /dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd
### Standard User Button Config
# UserButton: 6
### Trackball/Joystick input
# TrackballUp: 6
# TrackballDown: 19
# TrackballLeft: 5
# TrackballRight: 26
# TrackballPress: 13
###
Logging:
@@ -188,11 +187,18 @@ Logging:
# AsciiLogs: true # default if not specified is !isatty() on stdout
Webserver:
# Port: 443 # Port for Webserver & Webservices
# Port: 9443 # Port for Webserver & Webservices
# RootPath: /usr/share/meshtasticd/web # Root Dir of WebServer
# SSLKey: /etc/meshtasticd/ssl/private_key.pem # Path to SSL Key, generated if not present
# SSLCert: /etc/meshtasticd/ssl/certificate.pem # Path to SSL Certificate, generated if not present
HostMetrics:
# ReportInterval: 30 # Interval in minutes between HostMetrics report packets, or 0 for disabled
# Channel: 0 # channel to send Host Metrics over. Defaults to the primary channel.
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
General:
MaxNodes: 200
MaxMessageQueue: 100

View File

@@ -0,0 +1,26 @@
### Waveshare 1.44inch LCD HAT
Display:
Panel: ST7735S
spidev: spidev0.0 # Specify either the spidev here, or the CS below
# CS: 8 #Chip Select # Optional, as this is the default pin for spidev0.0
DC: 25 # Data/Command pin
Backlight: 24
Width: 128
Height: 128
Reset: 27
OffsetX: 2
OffsetY: 1
# OffsetY: 31 # These two options are used to properly flip the screen 180 degrees
# OffsetRotate: 3
Input:
TrackballUp: 6
TrackballDown: 19
TrackballLeft: 5
TrackballRight: 26
TrackballPress: 13
TrackballDirection: FALLING
# User: 21

View File

@@ -6,6 +6,6 @@ Lora:
IRQ: 16
Busy: 20
Reset: 24
TXen: 13
RXen: 12
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true

View File

@@ -0,0 +1,21 @@
Lora:
### RAK13300in Slot 1
Module: sx1262
IRQ: 22 #IO6
Reset: 16 # IO4
Busy: 24 # IO5
# Ant_sw: 13 # IO3
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: 8
### RAK13300in Slot 2 pins
# IRQ: 18 #IO6
# Reset: 24 # IO4
# Busy: 19 # IO5
# # Ant_sw: 23 # IO3
# spidev: spidev0.1
# # CS: 7

View File

@@ -0,0 +1,18 @@
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
gpiochip: 0
MOSI: 12
MISO: 13
IRQ: 1
Busy: 23
Reset: 22
RXen: 0
gpiochip: 1
CS: 9
SCK: 11
# TXen: bridge to DIO2 on E22 module
SX126X_MAX_POWER: 22
spidev: spidev1.0
spiSpeed: 2000000

View File

@@ -5,13 +5,13 @@ TITLE Meshtastic device-install
SET "SCRIPT_NAME=%~nx0"
SET "DEBUG=0"
SET "PYTHON="
SET "WEB_APP=0"
SET "TFT_BUILD=0"
SET "BIGDB8=0"
SET "BIGDB16=0"
SET "ESPTOOL_BAUD=115200"
SET "ESPTOOL_CMD="
SET "LOGCOUNTER=0"
SET "BPS_RESET=0"
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone"
@@ -24,7 +24,7 @@ GOTO getopts
:help
ECHO Flash image file to device, but first erasing and writing system information.
ECHO.
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] (--web)
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] [--1200bps-reset]
ECHO.
ECHO Options:
ECHO -f filename The firmware .bin file to flash. Custom to your device type and region. (required)
@@ -34,14 +34,16 @@ ECHO If not set, ESPTOOL iterates all ports (Dangerous).
ECHO -P python Specify alternate python interpreter to use to invoke esptool. (default: python)
ECHO If supplied the script will use python.
ECHO If not supplied the script will try to find esptool in Path.
ECHO --web Enable WebUI. (default: false)
ECHO --1200bps-reset Attempt to place the device in correct mode. (1200bps Reset)
ECHO Some hardware requires this twice.
ECHO.
ECHO Example: %SCRIPT_NAME% -p COM17 --1200bps-reset
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4.bin -p COM11
ECHO Example: %SCRIPT_NAME% -f firmware-unphone-2.6.0.0b106d4.bin -p COM11 --web
ECHO Example: %SCRIPT_NAME% -f firmware-unphone-2.6.0.0b106d4.bin -p COM11
GOTO eof
:version
ECHO %SCRIPT_NAME% [Version 2.6.1]
ECHO %SCRIPT_NAME% [Version 2.6.2]
ECHO Meshtastic
GOTO eof
@@ -57,11 +59,13 @@ IF /I "%~1"=="-f" SET "FILENAME=%~2" & SHIFT
IF "%~1"=="-p" SET "ESPTOOL_PORT=%~2" & SHIFT
IF /I "%~1"=="--port" SET "ESPTOOL_PORT=%~2" & SHIFT
IF "%~1"=="-P" SET "PYTHON=%~2" & SHIFT
IF /I "%~1"=="--web" SET "WEB_APP=1"
IF /I "%~1"=="--1200bps-reset" SET "BPS_RESET=1"
SHIFT
GOTO getopts
:endopts
IF %BPS_RESET% EQU 1 GOTO skip-filename
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..."
IF "__!FILENAME!__"=="____" (
CALL :LOG_MESSAGE DEBUG "Missing -f filename input."
@@ -95,6 +99,9 @@ IF NOT "!FILENAME:update=!"=="!FILENAME!" (
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *update* file. !FILENAME!"
)
:skip-filename
SET "ESPTOOL_BAUD=1200"
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
IF NOT "__%PYTHON%__"=="____" (
SET "ESPTOOL_CMD=!PYTHON! -m esptool"
@@ -133,13 +140,16 @@ IF "__!ESPTOOL_PORT!__" == "____" (
)
CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
IF %BPS_RESET% EQU 1 (
@REM Attempt to change mode via 1200bps Reset.
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
GOTO eof
)
@REM Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
@REM https://github.com/meshtastic/web-flasher/blob/main/types/resources.ts#L3
IF NOT "!FILENAME:-tft-=!"=="!FILENAME!" (
CALL :LOG_MESSAGE DEBUG "We are working with a *-tft-* file. !FILENAME!"
IF %WEB_APP% EQU 1 (
CALL :LOG_MESSAGE ERROR "Cannot enable WebUI (--web) and MUI." & GOTO eof
)
SET "TFT_BUILD=1"
) ELSE (
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *-tft-* file. !FILENAME!"
@@ -193,13 +203,8 @@ SET "OTA_FILENAME=bleota.bin"
:end_loop_c3
CALL :LOG_MESSAGE DEBUG "Set OTA_FILENAME to: !OTA_FILENAME!"
@REM Check if (--web) is enabled and prefix BASENAME with "littlefswebui-" else "littlefs-".
IF %WEB_APP% EQU 1 (
CALL :LOG_MESSAGE INFO "WebUI selected."
SET "SPIFFS_FILENAME=littlefswebui-%BASENAME%"
) ELSE (
SET "SPIFFS_FILENAME=littlefs-%BASENAME%"
)
@REM Set SPIFFS filename with "littlefs-" prefix.
SET "SPIFFS_FILENAME=littlefs-%BASENAME%"
CALL :LOG_MESSAGE DEBUG "Set SPIFFS_FILENAME to: !SPIFFS_FILENAME!"
@REM Default offsets.
@@ -254,6 +259,7 @@ EXIT /B %ERRORLEVEL%
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
CALL :RESET_ERROR
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
IF %BPS_RESET% EQU 1 GOTO :eof
IF %ERRORLEVEL% NEQ 0 (
CALL :LOG_MESSAGE ERROR "Error running command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
EXIT /B %ERRORLEVEL%

View File

@@ -1,13 +1,18 @@
#!/bin/bash
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
WEB_APP=false
BPS_RESET=false
TFT_BUILD=false
MCU=""
# Variant groups
BIGDB_8MB=(
"picomputer-s3"
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
if [[ $FILENAME == *"-tft-"* ]]; then
TFT_BUILD=true
fi
# Extract BASENAME from %FILENAME% for later use.r-s3"
"unphone"
"seeed-sensecap-indicator"
"crowpanel-esp32s3"
@@ -32,175 +37,185 @@ BIGDB_16MB=(
"ESP32-S3-Pico"
"m5stack-cores3"
"station-g2"
"t-eth-elite"
"t-watch-s3"
"t-eth-elite"
"t-watch-s3"
"elecrow-adv-35-tft"
"elecrow-adv-24-28-tft"
"elecrow-adv1-43-50-70-tft"
)
S3_VARIANTS=(
"s3"
"-v3"
"t-deck"
"wireless-paper"
"wireless-tracker"
"station-g2"
"unphone"
"s3"
"-v3"
"t-deck"
"wireless-paper"
"wireless-tracker"
"station-g2"
"unphone"
"t-eth-elite"
"mesh-tab"
"dreamcatcher"
"ESP32-S3-Pico"
"seeed-sensecap-indicator"
"heltec_capsule_sensor_v3"
"vision-master"
"icarus"
"tracksenger"
"elecrow-adv"
)
# Determine the correct esptool command to use
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
ESPTOOL_CMD="$PYTHON -m esptool"
ESPTOOL_CMD="$PYTHON -m esptool"
elif command -v esptool >/dev/null 2>&1; then
ESPTOOL_CMD="esptool"
ESPTOOL_CMD="esptool"
elif command -v esptool.py >/dev/null 2>&1; then
ESPTOOL_CMD="esptool.py"
ESPTOOL_CMD="esptool.py"
else
echo "Error: esptool not found"
exit 1
echo "Error: esptool not found"
exit 1
fi
set -e
# Usage info
show_help() {
cat <<EOF
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME] [--web]
cat <<EOF
Usage: $(basename "$0") [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME] [--1200bps-reset]
Flash image file to device, but first erasing and writing system information.
-h Display this help and exit.
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous).
-P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON")
-f FILENAME The firmware .bin file to flash. Custom to your device type and region.
--web Enable WebUI. (Default: false)
--1200bps-reset Attempt to place the device in correct mode. Some hardware requires this twice. (1200bps Reset)
EOF
}
# Parse arguments using a single while loop
while [ $# -gt 0 ]; do
case "$1" in
-h | --help)
show_help
exit 0
;;
-p)
ESPTOOL_CMD="$ESPTOOL_CMD --port $2"
shift
;;
-P)
PYTHON="$2"
shift
;;
-f)
FILENAME="$2"
shift
;;
--web)
WEB_APP=true
;;
--) # Stop parsing options
shift
break
;;
*)
echo "Unknown argument: $1" >&2
exit 1
;;
esac
shift # Move to the next argument
case "$1" in
-h | --help)
show_help
exit 0
;;
-p)
ESPTOOL_CMD="$ESPTOOL_CMD --port $2"
shift
;;
-P)
PYTHON="$2"
shift
;;
-f)
FILENAME="$2"
shift
;;
--1200bps-reset)
BPS_RESET=true
;;
--) # Stop parsing options
shift
break
;;
*)
echo "Unknown argument: $1" >&2
exit 1
;;
esac
shift # Move to the next argument
done
[ -z "$FILENAME" -a -n "$1" ] && {
FILENAME=$1
shift
if [[ $BPS_RESET == true ]]; then
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
exit 0
fi
[ -z "$FILENAME" ] && [ -n "$1" ] && {
FILENAME="$1"
shift
}
if [[ $FILENAME != firmware-* ]]; then
if [[ "$FILENAME" != firmware-* ]]; then
echo "Filename must be a firmware-* file."
exit 1
fi
# Check if FILENAME contains "-tft-" and prevent web/mui comingling.
if [[ ${FILENAME//-tft-/} != "$FILENAME" ]]; then
TFT_BUILD=true
if [[ $WEB_APP == true ]] && [[ $TFT_BUILD == true ]]; then
echo "Cannot enable WebUI (--web) and MUI."
exit 1
fi
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
if [[ "${FILENAME//-tft-/}" != "$FILENAME" ]]; then
TFT_BUILD=true
fi
# Extract BASENAME from %FILENAME% for later use.
BASENAME="${FILENAME/firmware-/}"
if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
# Default littlefs* offset (--web).
OFFSET=0x300000
# Default littlefs* offset.
OFFSET=0x300000
# Default OTA Offset
OTA_OFFSET=0x260000
# Default OTA Offset
OTA_OFFSET=0x260000
# littlefs* offset for BigDB 8mb and OTA OFFSET.
for variant in "${BIGDB_8MB[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
OFFSET=0x670000
OTA_OFFSET=0x340000
fi
done
# littlefs* offset for BigDB 8mb and OTA OFFSET.
for variant in "${BIGDB_8MB[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
OFFSET=0x670000
OTA_OFFSET=0x340000
fi
done
# littlefs* offset for BigDB 16mb and OTA OFFSET.
for variant in "${BIGDB_16MB[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
OFFSET=0xc90000
OTA_OFFSET=0x650000
fi
done
# littlefs* offset for BigDB 16mb and OTA OFFSET.
for variant in "${BIGDB_16MB[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
OFFSET=0xc90000
OTA_OFFSET=0x650000
fi
done
# Account for S3 board's different OTA partition
# FIXME: Use PlatformIO info to determine MCU type, this is unmaintainable
for variant in "${S3_VARIANTS[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
MCU="esp32s3"
fi
done
# Account for S3 board's different OTA partition
# FIXME: Use PlatformIO info to determine MCU type, this is unmaintainable
for variant in "${S3_VARIANTS[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
MCU="esp32s3"
fi
done
if [ "$MCU" != "esp32s3" ]; then
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
OTAFILE=bleota.bin
else
OTAFILE=bleota-c3.bin
fi
else
OTAFILE=bleota-s3.bin
fi
if [ "$MCU" != "esp32s3" ]; then
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
OTAFILE=bleota.bin
else
OTAFILE=bleota-c3.bin
fi
else
OTAFILE=bleota-s3.bin
fi
# Check if WEB_APP (--web) is enabled and add "littlefswebui-" to BASENAME else "littlefs-".
if [ "$WEB_APP" = true ]; then
SPIFFSFILE=littlefswebui-${BASENAME}
else
SPIFFSFILE=littlefs-${BASENAME}
fi
# Set SPIFFS filename with "littlefs-" prefix.
SPIFFSFILE=littlefs-${BASENAME}
if [[ ! -f $FILENAME ]]; then
echo "Error: file ${FILENAME} wasn't found. Terminating."
exit 1
fi
if [[ ! -f $OTAFILE ]]; then
echo "Error: file ${OTAFILE} wasn't found. Terminating."
exit 1
fi
if [[ ! -f $SPIFFSFILE ]]; then
echo "Error: file ${SPIFFSFILE} wasn't found. Terminating."
exit 1
fi
if [[ ! -f "$FILENAME" ]]; then
echo "Error: file ${FILENAME} wasn't found. Terminating."
exit 1
fi
if [[ ! -f "$OTAFILE" ]]; then
echo "Error: file ${OTAFILE} wasn't found. Terminating."
exit 1
fi
if [[ ! -f "$SPIFFSFILE" ]]; then
echo "Error: file ${SPIFFSFILE} wasn't found. Terminating."
exit 1
fi
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
$ESPTOOL_CMD erase_flash
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
$ESPTOOL_CMD erase_flash
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
else
show_help
echo "Invalid file: ${FILENAME}"
show_help
echo "Invalid file: ${FILENAME}"
fi
exit 0

View File

@@ -8,12 +8,13 @@ SET "PYTHON="
SET "ESPTOOL_BAUD=115200"
SET "ESPTOOL_CMD="
SET "LOGCOUNTER=0"
SET "CHANGE_MODE=0"
GOTO getopts
:help
ECHO Flash image file to device, but leave existing system intact.
ECHO.
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python]
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] [--change-mode]
ECHO.
ECHO Options:
ECHO -f filename The update .bin file to flash. Custom to your device type and region. (required)
@@ -23,12 +24,15 @@ ECHO If not set, ESPTOOL iterates all ports (Dangerous).
ECHO -P python Specify alternate python interpreter to use to invoke esptool. (default: python)
ECHO If supplied the script will use python.
ECHO If not supplied the script will try to find esptool in Path.
ECHO --change-mode Attempt to place the device in correct mode. (1200bps Reset)
ECHO Some hardware requires this twice.
ECHO.
ECHO Example: %SCRIPT_NAME% -p COM17 --change-mode
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4-update.bin -p COM11
GOTO eof
:version
ECHO %SCRIPT_NAME% [Version 2.6.1]
ECHO %SCRIPT_NAME% [Version 2.6.2]
ECHO Meshtastic
GOTO eof
@@ -44,10 +48,13 @@ IF /I "%~1"=="-f" SET "FILENAME=%~2" & SHIFT
IF "%~1"=="-p" SET "ESPTOOL_PORT=%~2" & SHIFT
IF /I "%~1"=="--port" SET "ESPTOOL_PORT=%~2" & SHIFT
IF "%~1"=="-P" SET "PYTHON=%~2" & SHIFT
IF /I "%~1"=="--change-mode" SET "CHANGE_MODE=1"
SHIFT
GOTO getopts
:endopts
IF %CHANGE_MODE% EQU 1 GOTO skip-filename
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..."
IF "__!FILENAME!__"=="____" (
CALL :LOG_MESSAGE DEBUG "Missing -f filename input."
@@ -77,6 +84,9 @@ IF "!FILENAME:update=!"=="!FILENAME!" (
CALL :LOG_MESSAGE DEBUG "We are working with a *update* file. !FILENAME!"
)
:skip-filename
SET "ESPTOOL_BAUD=1200"
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
IF NOT "__%PYTHON%__"=="____" (
SET "ESPTOOL_CMD=!PYTHON! -m esptool"
@@ -115,6 +125,12 @@ IF "__!ESPTOOL_PORT!__" == "____" (
)
CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
IF %CHANGE_MODE% EQU 1 (
@REM Attempt to change mode via 1200bps Reset.
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
GOTO eof
)
@REM Flashing operations.
CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET 0x10000..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x10000 "!FILENAME!" || GOTO eof
@@ -135,6 +151,7 @@ EXIT /B %ERRORLEVEL%
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
CALL :RESET_ERROR
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
IF %CHANGE_MODE% EQU 1 GOTO :eof
IF %ERRORLEVEL% NEQ 0 (
CALL :LOG_MESSAGE ERROR "Error running command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
EXIT /B %ERRORLEVEL%

View File

@@ -1,6 +1,7 @@
#!/bin/sh
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
CHANGE_MODE=false
# Determine the correct esptool command to use
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
@@ -17,17 +18,30 @@ fi
# Usage info
show_help() {
cat << EOF
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME|FILENAME]
Usage: $(basename "$0") [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME|FILENAME] [--change-mode]
Flash image file to device, leave existing system intact."
-h Display this help and exit
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous).
-P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON")
-f FILENAME The *update.bin file to flash. Custom to your device type.
--change-mode Attempt to place the device in correct mode. Some hardware requires this twice. (1200bps Reset)
EOF
}
# Check for --change-mode and remove it from arguments
NEW_ARGS=""
for arg in "$@"; do
if [ "$arg" = "--change-mode" ]; then
CHANGE_MODE=true
else
NEW_ARGS="$NEW_ARGS \"\$arg\""
fi
done
# Reset positional parameters to filtered list
eval set -- $NEW_ARGS
while getopts ":hp:P:f:" opt; do
case "${opt}" in
@@ -36,13 +50,13 @@ while getopts ":hp:P:f:" opt; do
exit 0
;;
p) ESPTOOL_CMD="$ESPTOOL_CMD --port ${OPTARG}"
;;
;;
P) PYTHON=${OPTARG}
;;
f) FILENAME=${OPTARG}
;;
*)
echo "Invalid flag."
echo "Invalid flag."
show_help >&2
exit 1
;;
@@ -50,17 +64,22 @@ while getopts ":hp:P:f:" opt; do
done
shift "$((OPTIND-1))"
[ -z "$FILENAME" -a -n "$1" ] && {
FILENAME=$1
if [ "$CHANGE_MODE" = true ]; then
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
exit 0
fi
[ -z "$FILENAME" ] && [ -n "$1" ] && {
FILENAME="$1"
shift
}
if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
printf "Trying to flash update ${FILENAME}"
$ESPTOOL_CMD --baud 115200 write_flash 0x10000 ${FILENAME}
echo "Trying to flash update ${FILENAME}"
$ESPTOOL_CMD --baud 115200 write_flash 0x10000 "${FILENAME}"
else
show_help
echo "Invalid file: ${FILENAME}"
show_help
echo "Invalid file: ${FILENAME}"
fi
exit 0

View File

@@ -27,7 +27,7 @@ for subdir, dirs, files in os.walk(rootdir):
if c.startswith("env:"):
section = config[c].name[4:]
if "extends" in config[config[c].name]:
if config[config[c].name]["extends"] == options[0] + "_base":
if options[0] + "_base" in config[config[c].name]["extends"]:
if "board_level" in config[config[c].name]:
if (
config[config[c].name]["board_level"] == "extra"

View File

@@ -5,10 +5,11 @@ StartLimitInterval=200
StartLimitBurst=5
[Service]
User=root
Group=root
AmbientCapabilities=CAP_NET_BIND_SERVICE
User=meshtasticd
Group=meshtasticd
Type=simple
ExecStart=/usr/sbin/meshtasticd
ExecStart=/usr/bin/meshtasticd
Restart=always
RestartSec=3

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
cp "release/meshtasticd_linux_$(uname -m)" /usr/sbin/meshtasticd
cp "release/meshtasticd_linux_$(uname -m)" /usr/bin/meshtasticd
mkdir -p /etc/meshtasticd
if [[ -f "/etc/meshtasticd/config.yaml" ]]; then
cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml

View File

@@ -87,6 +87,30 @@
</screenshots>
<releases>
<release version="2.7.2" date="2025-07-04">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.2</url>
</release>
<release version="2.7.1" date="2025-06-27">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.1</url>
</release>
<release version="2.7.0" date="2025-06-20">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.0</url>
</release>
<release version="2.6.13" date="2025-06-16">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.13</url>
</release>
<release version="2.6.12" date="2025-06-15">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.12</url>
</release>
<release version="2.6.11" date="2025-06-02">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.11</url>
</release>
<release version="2.6.10" date="2025-05-25">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.10</url>
</release>
<release version="2.6.9" date="2025-05-15">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.9</url>
</release>
<release version="2.6.8" date="2025-05-05">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.8</url>
</release>

View File

@@ -131,3 +131,33 @@ for lb in env.GetLibBuilders():
if lb.name == "meshtastic-device-ui":
lb.env.Append(CPPDEFINES=[("APP_VERSION", verObj["long"])])
break
# Get the display resolution from macros
def get_display_resolution(build_flags):
# Check "DISPLAY_SIZE" to determine the screen resolution
for flag in build_flags:
if isinstance(flag, tuple) and flag[0] == "DISPLAY_SIZE":
screen_width, screen_height = map(int, flag[1].split("x"))
return screen_width, screen_height
print("No screen resolution defined in build_flags. Please define DISPLAY_SIZE.")
exit(1)
def load_boot_logo(source, target, env):
build_flags = env.get("CPPDEFINES", [])
logo_w, logo_h = get_display_resolution(build_flags)
print(f"TFT build with {logo_w}x{logo_h} resolution detected")
# Load the boot logo from `branding/logo_<width>x<height>.png` if it exists
source_path = join(env["PROJECT_DIR"], "branding", f"logo_{logo_w}x{logo_h}.png")
dest_dir = join(env["PROJECT_DIR"], "data", "boot")
dest_path = join(dest_dir, "logo.png")
if env.File(source_path).exists():
print(f"Loading boot logo from {source_path}")
# Prepare the destination
env.Execute(f"mkdir -p {dest_dir} && rm -f {dest_path}")
# Copy the logo to the `data/boot` directory
env.Execute(f"cp {source_path} {dest_path}")
# Load the boot logo on TFT builds
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
env.AddPreAction('$BUILD_DIR/littlefs.bin', load_boot_logo)

View File

@@ -1 +1 @@
2.5.3
2.6.4

View File

@@ -48,6 +48,6 @@
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "FIXME",
"url": "https://www.elecrow.com/thinknode-m1-meshtastic-lora-signal-transceiver-powered-by-nrf52840-with-154-screen-support-gps.html",
"vendor": "ELECROW"
}

View File

@@ -8,8 +8,8 @@
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_CDC_ON_BOOT=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=0"
],

View File

@@ -0,0 +1,52 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "GAT562 Mesh Trial Tracker",
"mcu": "nrf52840",
"variant": "gat562_mesh_trial_tracker",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino", "freertos"],
"name": "GAT562 Mesh Trial Tracker",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "http://www.gat-iot.com/",
"vendor": "GAT-IOT"
}

View File

@@ -48,6 +48,6 @@
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "FIXME",
"url": "https://heltec.org/project/mesh-node-t114/",
"vendor": "Heltec"
}

View File

@@ -10,7 +10,7 @@
"hwids": [["0x2886", "0x0059"]],
"usb_product": "XIAO-BOOT",
"mcu": "nrf52840",
"variant": "Seeed_Solar_Node",
"variant": "seeed_solar_node",
"bsp": {
"name": "adafruit"
},
@@ -31,7 +31,7 @@
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Seeed_Solar_Node",
"name": "seeed_solar_node",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,

View File

@@ -0,0 +1,57 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v7.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_MDBT50Q_RX -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x2886", "0x1668"],
["0x2886", "0x1667"]
],
"usb_product": "TRACKER L1",
"mcu": "nrf52840",
"variant": "seeed_wio_tracker_L1",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "7.3.0",
"sd_fwid": "0x0123"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "seeed_wio_tracker_L1",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap",
"blackmagic"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.seeedstudio.com/Wio-Tracker-L1-p-6477.html",
"vendor": "Seeed Studio"
}

View File

@@ -7,9 +7,7 @@
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_MDBT50Q_RX -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x2886", "0x0166"]
],
"hwids": [["0x2886", "0x0166"]],
"usb_product": "XIAO-BOOT",
"mcu": "nrf52840",
"variant": "seeed_xiao_nrf52840_kit",

View File

@@ -11,7 +11,8 @@
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
["0x239A", "0x802A"],
["0x2886", "0x0057"]
],
"usb_product": "T1000-E-BOOT",
"mcu": "nrf52840",

View File

@@ -0,0 +1,41 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi",
"partitions": "default_16MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DRAK3312",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DBOARD_HAS_PSRAM"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "dio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "rak3312"
},
"connectivity": ["wifi", "bluetooth"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "WisCore RAK3312 Board",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://www.rakwireless.com/en-us",
"vendor": "rakwireless"
}

17
branding/README.md Normal file
View File

@@ -0,0 +1,17 @@
# Meshtastic Branding / Whitelabeling
This directory is consumed during the creation of **event** firmware.
`bin/platformio-custom.py` determines the display resolution, and locates the corresponding `logo_<width>x<height>.png`.
Ex:
- `logo_800x480.png`
- `logo_480x480.png`
- `logo_480x320.png`
- `logo_320x480.png`
- `logo_320x240.png`
This file is copied to `data/boot/logo.png` before filesytem image compilation.
For additional examples see the [`event/defcon33` branch](https://github.com/meshtastic/firmware/tree/event/defcon33).

22
debian/changelog vendored
View File

@@ -1,4 +1,4 @@
meshtasticd (2.6.8.0) UNRELEASED; urgency=medium
meshtasticd (2.7.2.0) UNRELEASED; urgency=medium
[ Austin Lane ]
* Initial packaging
@@ -10,4 +10,22 @@ meshtasticd (2.6.8.0) UNRELEASED; urgency=medium
[ ]
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Tue, 06 May 2025 01:32:49 +0000
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Fri, 04 Jul 2025 11:58:01 +0000

6
debian/control vendored
View File

@@ -31,7 +31,9 @@ Rules-Requires-Root: no
Package: meshtasticd
Architecture: any
Depends: ${misc:Depends}, ${shlibs:Depends}
Depends: adduser,
${misc:Depends},
${shlibs:Depends}
Description: Meshtastic daemon for communicating with Meshtastic devices
Meshtastic is an off-grid text communication platform that uses inexpensive
LoRa radios.
LoRa radios.

View File

@@ -1,5 +1,6 @@
var/lib/meshtasticd
etc/meshtasticd
etc/meshtasticd/config.d
etc/meshtasticd/available.d
usr/share/meshtasticd/web
etc/meshtasticd/ssl
etc/meshtasticd/ssl

View File

@@ -1,8 +1,8 @@
.pio/build/native-tft/meshtasticd usr/sbin
.pio/build/native-tft/meshtasticd usr/bin
bin/config.yaml etc/meshtasticd
bin/config.d/* etc/meshtasticd/available.d
bin/meshtasticd.service lib/systemd/system
web/* usr/share/meshtasticd/web
web/* usr/share/meshtasticd/web

80
debian/meshtasticd.postinst vendored Executable file
View File

@@ -0,0 +1,80 @@
#!/bin/sh
# postinst script for meshtasticd
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <postinst> `configure' <most-recently-configured-version>
# * <old-postinst> `abort-upgrade' <new version>
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
# <new-version>
# * <postinst> `abort-remove'
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
# <failed-install-package> <version> `removing'
# <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
configure|reconfigure)
# create spi, gpio groups (for udev rules)
# these groups already exist on Raspberry Pi OS
getent group spi >/dev/null 2>/dev/null || addgroup --system spi
getent group gpio >/dev/null 2>/dev/null || addgroup --system gpio
# create a meshtasticd group and user
getent passwd meshtasticd >/dev/null 2>/dev/null || adduser --system --home /var/lib/meshtasticd --no-create-home meshtasticd
getent group meshtasticd >/dev/null 2>/dev/null || addgroup --system meshtasticd
adduser meshtasticd meshtasticd >/dev/null 2>/dev/null
adduser meshtasticd spi >/dev/null 2>/dev/null
adduser meshtasticd gpio >/dev/null 2>/dev/null
# add meshtasticd user to appropriate groups (if they exist)
getent group plugdev >/dev/null 2>/dev/null && adduser meshtasticd plugdev >/dev/null 2>/dev/null
getent group dialout >/dev/null 2>/dev/null && adduser meshtasticd dialout >/dev/null 2>/dev/null
getent group i2c >/dev/null 2>/dev/null && adduser meshtasticd i2c >/dev/null 2>/dev/null
getent group video >/dev/null 2>/dev/null && adduser meshtasticd video >/dev/null 2>/dev/null
getent group audio >/dev/null 2>/dev/null && adduser meshtasticd audio >/dev/null 2>/dev/null
getent group input >/dev/null 2>/dev/null && adduser meshtasticd input >/dev/null 2>/dev/null
# migrate /root/.portduino to /var/lib/meshtasticd/.portduino
# should only run once, upon upgrade from < 2.6.9
if [ -n "$2" ] && dpkg --compare-versions "$2" lt 2.6.9; then
if [ -d /root/.portduino ] && [ ! -e /var/lib/meshtasticd/.portduino ]; then
cp -r /root/.portduino /var/lib/meshtasticd/.portduino
echo "Migrated meshtasticd VFS from /root/.portduino to /var/lib/meshtasticd/.portduino"
echo "meshtasticd now runs as the 'meshtasticd' user, not 'root'."
echo "See https://github.com/meshtastic/firmware/pull/6718 for details"
fi
fi
if [ -d /var/lib/meshtasticd ]; then
chown -R meshtasticd:meshtasticd /var/lib/meshtasticd
fi
if [ -d /etc/meshtasticd ]; then
chown -R meshtasticd:meshtasticd /etc/meshtasticd
fi
if [ -d /usr/share/meshtasticd ]; then
chown -R meshtasticd:meshtasticd /usr/share/meshtasticd
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

41
debian/meshtasticd.postrm vendored Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/sh
# postrm script for meshtasticd
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <postrm> `remove'
# * <postrm> `purge'
# * <old-postrm> `upgrade' <new-version>
# * <new-postrm> `failed-upgrade' <old-version>
# * <new-postrm> `abort-install'
# * <new-postrm> `abort-install' <old-version>
# * <new-postrm> `abort-upgrade' <old-version>
# * <disappearer's-postrm> `disappear' <overwriter>
# <overwriter-version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
# Only remove /var/lib/meshtasticd on purge
if [ "${1}" = "purge" ] ; then
rm -rf /var/lib/meshtasticd
fi
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

7
debian/meshtasticd.udev vendored Normal file
View File

@@ -0,0 +1,7 @@
# Set spidev ownership to 'spi' group
SUBSYSTEM=="spidev", KERNEL=="spidev*", GROUP="spi", MODE="0660"
# Allow access to USB CH341 devices
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="5512", MODE="0666"
# Set gpio ownership to 'gpio' group
SUBSYSTEM=="*gpiomem*", GROUP="gpio", MODE="0660"
SUBSYSTEM=="gpio", GROUP="gpio", MODE="0660"

View File

@@ -10,6 +10,8 @@
# - https://docs.pagure.org/rpkg-util/v3/index.html
# - https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/
%global meshtasticd_user meshtasticd
Name: meshtasticd
# Version Ex: 2.5.19
Version: {{{ meshtastic_version }}}
@@ -47,6 +49,8 @@ BuildRequires: pkgconfig(x11)
BuildRequires: pkgconfig(libinput)
BuildRequires: pkgconfig(xkbcommon-x11)
Requires: systemd-udev
%description
Meshtastic daemon for controlling Meshtastic devices. Meshtastic is an off-grid
text communication platform that uses inexpensive LoRa radios.
@@ -63,15 +67,25 @@ gzip -dr web
platformio run -e native-tft
%install
mkdir -p %{buildroot}%{_sbindir}
install -m 0755 .pio/build/native-tft/program %{buildroot}%{_sbindir}/meshtasticd
# Install meshtasticd binary
mkdir -p %{buildroot}%{_bindir}
install -m 0755 .pio/build/native-tft/program %{buildroot}%{_bindir}/meshtasticd
# Install portduino VFS dir
install -p -d -m 0770 %{buildroot}%{_localstatedir}/lib/meshtasticd
# Install udev rules
mkdir -p %{buildroot}%{_udevrulesdir}
install -m 0644 bin/99-meshtasticd-udev.rules %{buildroot}%{_udevrulesdir}/99-meshtasticd-udev.rules
# Install config dirs
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd
install -m 0644 bin/config-dist.yaml %{buildroot}%{_sysconfdir}/meshtasticd/config.yaml
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/config.d
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/available.d
cp -r bin/config.d/* %{buildroot}%{_sysconfdir}/meshtasticd/available.d
# Install systemd service
install -D -m 0644 bin/meshtasticd.service %{buildroot}%{_unitdir}/meshtasticd.service
# Install the web files under /usr/share/meshtasticd/web
@@ -80,10 +94,54 @@ cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
# Install default SSL storage directory (for web)
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/ssl
%pre
# create spi group (for udev rules)
getent group spi > /dev/null || groupadd -r spi
# create a meshtasticd group and user
getent group %{meshtasticd_user} > /dev/null || groupadd -r %{meshtasticd_user}
getent passwd %{meshtasticd_user} > /dev/null || \
useradd -r -d %{_localstatedir}/lib/meshtasticd -g %{meshtasticd_user} -G spi \
-s /sbin/nologin -c "Meshtastic Daemon" %{meshtasticd_user}
# add meshtasticd user to appropriate groups (if they exist)
getent group gpio > /dev/null && usermod -a -G gpio %{meshtasticd_user} > /dev/null
getent group plugdev > /dev/null && usermod -a -G plugdev %{meshtasticd_user} > /dev/null
getent group dialout > /dev/null && usermod -a -G dialout %{meshtasticd_user} > /dev/null
getent group i2c > /dev/null && usermod -a -G i2c %{meshtasticd_user} > /dev/null
getent group video > /dev/null && usermod -a -G video %{meshtasticd_user} > /dev/null
getent group audio > /dev/null && usermod -a -G audio %{meshtasticd_user} > /dev/null
getent group input > /dev/null && usermod -a -G input %{meshtasticd_user} > /dev/null
exit 0
%triggerin -- meshtasticd < 2.6.9
# migrate .portduino (if it exists and hasnt already been copied)
if [ -d /root/.portduino ] && [ ! -e /var/lib/meshtasticd/.portduino ]; then
mkdir -p /var/lib/meshtasticd
cp -r /root/.portduino /var/lib/meshtasticd/.portduino
chown -R %{meshtasticd_user}:%{meshtasticd_user} \
%{_localstatedir}/lib/meshtasticd || :
# Fix SELinux labels if present (no-op on non-SELinux systems)
restorecon -R /var/lib/meshtasticd/.portduino 2>/dev/null || :
echo "Migrated meshtasticd VFS from /root/.portduino to /var/lib/meshtasticd/.portduino"
echo "meshtasticd now runs as the 'meshtasticd' user, not 'root'."
echo "See https://github.com/meshtastic/firmware/pull/6718 for details"
fi
%post
%systemd_post meshtasticd.service
%preun
%systemd_preun meshtasticd.service
%postun
%systemd_postun_with_restart meshtasticd.service
%files
%defattr(-,%{meshtasticd_user},%{meshtasticd_user})
%license LICENSE
%doc README.md
%{_sbindir}/meshtasticd
%{_bindir}/meshtasticd
%dir %{_localstatedir}/lib/meshtasticd
%{_udevrulesdir}/99-meshtasticd-udev.rules
%dir %{_sysconfdir}/meshtasticd
%dir %{_sysconfdir}/meshtasticd/config.d
%dir %{_sysconfdir}/meshtasticd/available.d
@@ -96,4 +154,4 @@ mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/ssl
%dir %{_sysconfdir}/meshtasticd/ssl
%changelog
%autochangelog
%autochangelog

View File

@@ -49,10 +49,11 @@ build_flags = -Wno-missing-field-initializers
-DMESHTASTIC_EXCLUDE_DROPZONE=1
-DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1
-DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
#-DBUILD_EPOCH=$UNIX_TIME
#-D OLED_PL=1
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
#-DBUILD_EPOCH=$UNIX_TIME
#-D OLED_PL=1
monitor_speed = 115200
monitor_filters = direct
@@ -103,12 +104,12 @@ lib_deps =
[radiolib_base]
lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
jgromes/RadioLib@7.1.2
jgromes/RadioLib@7.2.0
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/35576e131e250f259878ea81819a90df837d1307.zip
https://github.com/meshtastic/device-ui/archive/8c7092c73425adfda1aac8c6960df06cd85f6d92.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
@@ -122,7 +123,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BMP085 packageName=adafruit/library/Adafruit BMP085 Library
adafruit/Adafruit BMP085 Library@1.2.4
# renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library
adafruit/Adafruit BME280 Library@2.2.4
adafruit/Adafruit BME280 Library@2.3.0
# renovate: datasource=custom.pio depName=Adafruit DPS310 packageName=adafruit/library/Adafruit DPS310
adafruit/Adafruit DPS310@1.1.5
# renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library
@@ -132,7 +133,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3
# renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor
adafruit/Adafruit PM25 AQI Sensor@1.2.0
adafruit/Adafruit PM25 AQI Sensor@2.0.0
# renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050
adafruit/Adafruit MPU6050@2.2.6
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
@@ -147,8 +148,6 @@ lib_deps =
emotibit/EmotiBit MLX90632@1.0.8
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
adafruit/Adafruit MLX90614 Library@2.1.5
# renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library
boschsensortec/BME68x Sensor Library@1.2.40408
# renovate: datasource=github-tags depName=INA3221 packageName=KodinLanewave/INA3221
https://github.com/KodinLanewave/INA3221/archive/1.0.1.zip
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
@@ -161,6 +160,14 @@ lib_deps =
robtillaart/INA226@0.6.4
# renovate: datasource=custom.pio depName=SparkFun MAX3010x packageName=sparkfun/library/SparkFun MAX3010x Pulse and Proximity Sensor Library
sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2
# renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library
sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@1.3.2
# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
adafruit/Adafruit LTR390 Library@1.1.2
# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
adafruit/Adafruit PCT2075@1.0.5
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
dfrobot/DFRobot_BMM150@1.0.0
; (not included in native / portduino)
[environmental_extra]
@@ -181,11 +188,15 @@ lib_deps =
adafruit/Adafruit SHT4x Library@1.0.5
# renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library
sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6
# renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library
sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@1.3.0
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
ClosedCube OPT3001@1.1.2
# renovate: datasource=git-refs depName=Bosch BSEC2 packageName=https://github.com/meshtastic/Bosch-BSEC2-Library gitBranch=extra_script
https://github.com/meshtastic/Bosch-BSEC2-Library/archive/e16952dfe5addd4287e1eb8c4f6ecac0fa3dd3de.zip
# renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2
boschsensortec/bsec2@1.10.2610
# renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library
boschsensortec/BME68x Sensor Library@1.3.40408
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
sensirion/Sensirion Core@0.7.1
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0

View File

@@ -9,15 +9,21 @@
"workarounds:all"
],
"forkProcessing": "enabled",
"ignoreDeps": ["protobufs"],
"ignoreDeps": [
"protobufs"
],
"git-submodules": {
"enabled": true
},
"pip_requirements": {
"fileMatch": ["bin/bump_metainfo/requirements.txt"]
"managerFilePatterns": [
"/bin/bump_metainfo/requirements.txt/"
]
},
"commitMessageTopic": "{{depName}}",
"labels": ["dependencies"],
"labels": [
"dependencies"
],
"customDatasources": {
"pio": {
"description": "PlatformIO Registry",
@@ -32,8 +38,12 @@
{
"customType": "regex",
"description": "Match meshtastic/web version",
"fileMatch": ["bin/web.version"],
"matchStrings": ["(?<currentValue>.+)$"],
"managerFilePatterns": [
"/bin/web.version/"
],
"matchStrings": [
"(?<currentValue>.+)$"
],
"datasourceTemplate": "github-releases",
"depNameTemplate": "meshtastic/web",
"versioningTemplate": "semver-coerced"
@@ -41,7 +51,9 @@
{
"customType": "regex",
"description": "Match normal PIO dependencies",
"fileMatch": [".*\\.ini$"],
"managerFilePatterns": [
"/.*\\.ini$/"
],
"matchStrings": [
"# renovate: datasource=(?<datasource>.*?)(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\s+?.+?@(?<currentValue>.+?)\\s"
],
@@ -50,9 +62,11 @@
{
"customType": "regex",
"description": "Match PIO zipped dependencies with github tag ref",
"fileMatch": [".*\\.ini$"],
"managerFilePatterns": [
"/.*\\.ini$/"
],
"matchStrings": [
"# renovate: datasource=github-tags(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\s+?https:\/\/.+?archive\/(?<currentValue>.+?).zip\\s"
"# renovate: datasource=github-tags(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\s+?https://.+?archive/(?<currentValue>.+?).zip\\s"
],
"datasourceTemplate": "github-tags",
"versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver-coerced{{/if}}"
@@ -60,9 +74,11 @@
{
"customType": "regex",
"description": "Match PIO zipped dependencies with git commit ref",
"fileMatch": [".*\\.ini$"],
"managerFilePatterns": [
"/.*\\.ini$/"
],
"matchStrings": [
"# renovate: datasource=git-refs(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\sgitBranch=(?<gitBranch>.+?)\\s+?https:\/\/.+?archive\/(?<currentDigest>.+?).zip\\s"
"# renovate: datasource=git-refs(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\sgitBranch=(?<gitBranch>.+?)\\s+?https://.+?archive/(?<currentDigest>.+?).zip\\s"
],
"datasourceTemplate": "git-refs",
"currentValueTemplate": "{{{gitBranch}}}",
@@ -71,8 +87,12 @@
],
"packageRules": [
{
"matchDepNames": ["meshtastic/device-ui"],
"reviewers": ["mverch67"],
"matchDepNames": [
"meshtastic/device-ui"
],
"reviewers": [
"mverch67"
],
"changelogUrl": "https://github.com/meshtastic/device-ui/compare/{{currentDigest}}...{{newDigest}}"
}
]

View File

@@ -59,82 +59,82 @@ class AmbientLightingThread : public concurrency::OSThread
return;
}
LOG_DEBUG("AmbientLighting init");
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
#ifdef HAS_NCP5623
if (_type == ScanI2C::NCP5623) {
rgb.begin();
#endif
#ifdef HAS_LP5562
} else if (_type == ScanI2C::LP5562) {
rgbw.begin();
if (_type == ScanI2C::LP5562) {
rgbw.begin();
#endif
#ifdef RGBLED_RED
pinMode(RGBLED_RED, OUTPUT);
pinMode(RGBLED_GREEN, OUTPUT);
pinMode(RGBLED_BLUE, OUTPUT);
pinMode(RGBLED_RED, OUTPUT);
pinMode(RGBLED_GREEN, OUTPUT);
pinMode(RGBLED_BLUE, OUTPUT);
#endif
#ifdef HAS_NEOPIXEL
pixels.begin(); // Initialise the pixel(s)
pixels.clear(); // Set all pixel colors to 'off'
pixels.setBrightness(moduleConfig.ambient_lighting.current);
pixels.begin(); // Initialise the pixel(s)
pixels.clear(); // Set all pixel colors to 'off'
pixels.setBrightness(moduleConfig.ambient_lighting.current);
#endif
setLighting();
setLighting();
#endif
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
}
}
#endif
}
}
protected:
int32_t runOnce() override
{
protected:
int32_t runOnce() override
{
#ifdef HAS_RGB_LED
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
if ((_type == ScanI2C::NCP5623 || _type == ScanI2C::LP5562) && moduleConfig.ambient_lighting.led_state) {
if ((_type == ScanI2C::NCP5623 || _type == ScanI2C::LP5562) && moduleConfig.ambient_lighting.led_state) {
#endif
setLighting();
return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification
setLighting();
return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
}
#endif
#endif
return disable();
}
#endif
#endif
return disable();
}
// When shutdown() is issued, setLightingOff will be called.
CallbackObserver<AmbientLightingThread, void *> notifyDeepSleepObserver =
CallbackObserver<AmbientLightingThread, void *>(this, &AmbientLightingThread::setLightingOff);
// When shutdown() is issued, setLightingOff will be called.
CallbackObserver<AmbientLightingThread, void *> notifyDeepSleepObserver =
CallbackObserver<AmbientLightingThread, void *>(this, &AmbientLightingThread::setLightingOff);
private:
ScanI2C::DeviceType _type = ScanI2C::DeviceType::NONE;
private:
ScanI2C::DeviceType _type = ScanI2C::DeviceType::NONE;
// Turn RGB lighting off, is used in junction to shutdown()
int setLightingOff(void *unused)
{
// Turn RGB lighting off, is used in junction to shutdown()
int setLightingOff(void *unused)
{
#ifdef HAS_NCP5623
rgb.setCurrent(0);
rgb.setRed(0);
rgb.setGreen(0);
rgb.setBlue(0);
LOG_INFO("OFF: NCP5623 Ambient lighting");
rgb.setCurrent(0);
rgb.setRed(0);
rgb.setGreen(0);
rgb.setBlue(0);
LOG_INFO("OFF: NCP5623 Ambient lighting");
#endif
#ifdef HAS_LP5562
rgbw.setCurrent(0);
rgbw.setRed(0);
rgbw.setGreen(0);
rgbw.setBlue(0);
rgbw.setWhite(0);
LOG_INFO("OFF: LP5562 Ambient lighting");
rgbw.setCurrent(0);
rgbw.setRed(0);
rgbw.setGreen(0);
rgbw.setBlue(0);
rgbw.setWhite(0);
LOG_INFO("OFF: LP5562 Ambient lighting");
#endif
#ifdef HAS_NEOPIXEL
pixels.clear();
pixels.show();
LOG_INFO("OFF: NeoPixel Ambient lighting");
pixels.clear();
pixels.show();
LOG_INFO("OFF: NeoPixel Ambient lighting");
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - 0);
analogWrite(RGBLED_GREEN, 255 - 0);
analogWrite(RGBLED_BLUE, 255 - 0);
LOG_INFO("OFF: Ambient light RGB Common Anode");
analogWrite(RGBLED_RED, 255 - 0);
analogWrite(RGBLED_GREEN, 255 - 0);
analogWrite(RGBLED_BLUE, 255 - 0);
LOG_INFO("OFF: Ambient light RGB Common Anode");
#elif defined(RGBLED_RED)
analogWrite(RGBLED_RED, 0);
analogWrite(RGBLED_GREEN, 0);
@@ -142,56 +142,57 @@ class AmbientLightingThread : public concurrency::OSThread
LOG_INFO("OFF: Ambient light RGB Common Cathode");
#endif
#ifdef UNPHONE
unphone.rgb(0, 0, 0);
LOG_INFO("OFF: unPhone Ambient lighting");
unphone.rgb(0, 0, 0);
LOG_INFO("OFF: unPhone Ambient lighting");
#endif
return 0;
}
return 0;
}
void setLighting()
{
void setLighting()
{
#ifdef HAS_NCP5623
rgb.setCurrent(moduleConfig.ambient_lighting.current);
rgb.setRed(moduleConfig.ambient_lighting.red);
rgb.setGreen(moduleConfig.ambient_lighting.green);
rgb.setBlue(moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init NCP5623 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.current,
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
rgb.setCurrent(moduleConfig.ambient_lighting.current);
rgb.setRed(moduleConfig.ambient_lighting.red);
rgb.setGreen(moduleConfig.ambient_lighting.green);
rgb.setBlue(moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init NCP5623 Ambient light w/ current=%d, red=%d, green=%d, blue=%d",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef HAS_LP5562
rgbw.setCurrent(moduleConfig.ambient_lighting.current);
rgbw.setRed(moduleConfig.ambient_lighting.red);
rgbw.setGreen(moduleConfig.ambient_lighting.green);
rgbw.setBlue(moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init LP5562 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.current,
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
rgbw.setCurrent(moduleConfig.ambient_lighting.current);
rgbw.setRed(moduleConfig.ambient_lighting.red);
rgbw.setGreen(moduleConfig.ambient_lighting.green);
rgbw.setBlue(moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init LP5562 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.current,
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef HAS_NEOPIXEL
pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue),
0, NEOPIXEL_COUNT);
pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue),
0, NEOPIXEL_COUNT);
// RadioMaster Bandit has addressable LED at the two buttons
// this allow us to set different lighting for them in variant.h file.
#ifdef RADIOMASTER_900_BANDIT
#if defined(BUTTON1_COLOR) && defined(BUTTON1_COLOR_INDEX)
pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1);
pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1);
#endif
#if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX)
pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1);
pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1);
#endif
#endif
pixels.show();
LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue);
pixels.show();
LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green);
analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green);
analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#elif defined(RGBLED_RED)
analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red);
analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green);
@@ -200,11 +201,12 @@ class AmbientLightingThread : public concurrency::OSThread
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef UNPHONE
unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
}
};
}
};
} // namespace concurrency

View File

@@ -47,6 +47,20 @@ class AudioThread : public concurrency::OSThread
setCPUFast(false);
}
void readAloud(const char *text)
{
if (i2sRtttl != nullptr) {
i2sRtttl->stop();
delete i2sRtttl;
i2sRtttl = nullptr;
}
ESP8266SAM *sam = new ESP8266SAM;
sam->Say(audioOut, text);
delete sam;
setCPUFast(false);
}
protected:
int32_t runOnce() override
{

View File

@@ -88,10 +88,16 @@ class BluetoothStatus : public Status
break;
case ConnectionState::CONNECTED:
LOG_DEBUG("BluetoothStatus CONNECTED");
#ifdef BLE_LED
digitalWrite(BLE_LED, HIGH);
#endif
break;
case ConnectionState::DISCONNECTED:
LOG_DEBUG("BluetoothStatus DISCONNECTED");
#ifdef BLE_LED
digitalWrite(BLE_LED, LOW);
#endif
break;
}
}
@@ -102,4 +108,4 @@ class BluetoothStatus : public Status
} // namespace meshtastic
extern meshtastic::BluetoothStatus *bluetoothStatus;
extern meshtastic::BluetoothStatus *bluetoothStatus;

View File

@@ -1,458 +0,0 @@
#include "ButtonThread.h"
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
#endif
#include "MeshService.h"
#include "PowerFSM.h"
#include "RadioLibInterface.h"
#include "buzz.h"
#include "main.h"
#include "modules/ExternalNotificationModule.h"
#include "power.h"
#include "sleep.h"
#ifdef ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
#endif
#define DEBUG_BUTTONS 0
#if DEBUG_BUTTONS
#define LOG_BUTTON(...) LOG_DEBUG(__VA_ARGS__)
#else
#define LOG_BUTTON(...)
#endif
using namespace concurrency;
ButtonThread *buttonThread; // Declared extern in header
volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BUTTON_EVENT_NONE;
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN)
OneButton ButtonThread::userButton; // Get reference to static member
#endif
ButtonThread::ButtonThread() : OSThread("Button")
{
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN)
#if defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
this->userButton = OneButton(settingsMap[user], true, true);
LOG_DEBUG("Use GPIO%02d for button", settingsMap[user]);
}
#elif defined(BUTTON_PIN)
#if !defined(USERPREFS_BUTTON_PIN)
int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin
#endif
#ifdef USERPREFS_BUTTON_PIN
int pin = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN; // Resolved button pin
#endif
#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
this->userButton = OneButton(pin, false, false);
#elif defined(BUTTON_ACTIVE_LOW)
this->userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP);
#else
this->userButton = OneButton(pin, true, true);
#endif
LOG_DEBUG("Use GPIO%02d for button", pin);
#endif
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
#ifdef BUTTON_SENSE_TYPE
pinMode(pin, BUTTON_SENSE_TYPE);
#else
pinMode(pin, INPUT_PULLUP_SENSE);
#endif
#endif
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN)
userButton.attachClick(userButtonPressed);
userButton.setClickMs(BUTTON_CLICK_MS);
userButton.setPressMs(BUTTON_LONGPRESS_MS);
userButton.setDebounceMs(1);
userButton.attachDoubleClick(userButtonDoublePressed);
userButton.attachMultiClick(userButtonMultiPressed, this); // Reference to instance: get click count from non-static OneButton
#if !defined(T_DECK) && \
!defined( \
ELECROW_ThinkNode_M2) // T-Deck immediately wakes up after shutdown, Thinknode M2 has this on the smaller ALT button
userButton.attachLongPressStart(userButtonPressedLongStart);
userButton.attachLongPressStop(userButtonPressedLongStop);
#endif
#endif
#ifdef BUTTON_PIN_ALT
#if defined(ELECROW_ThinkNode_M2)
this->userButtonAlt = OneButton(BUTTON_PIN_ALT, false, false);
#else
this->userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
#endif
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
#endif
userButtonAlt.attachClick(userButtonPressedScreen);
userButtonAlt.setClickMs(BUTTON_CLICK_MS);
userButtonAlt.setPressMs(BUTTON_LONGPRESS_MS);
userButtonAlt.setDebounceMs(1);
userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
userButtonAlt.attachLongPressStop(userButtonPressedLongStop);
#endif
#ifdef BUTTON_PIN_TOUCH
userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
userButtonTouch.setPressMs(BUTTON_TOUCH_MS);
userButtonTouch.attachLongPressStart(touchPressedLongStart); // Better handling with longpress than click?
#endif
#ifdef ARCH_ESP32
// Register callbacks for before and after lightsleep
// Used to detach and reattach interrupts
lsObserver.observe(&notifyLightSleep);
lsEndObserver.observe(&notifyLightSleepEnd);
#endif
attachButtonInterrupts();
#endif
}
void ButtonThread::switchPage()
{
#ifdef BUTTON_PIN
#if !defined(USERPREFS_BUTTON_PIN)
if (((config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN) !=
moduleConfig.canned_message.inputbroker_pin_press) ||
!(moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.rotary1_enabled) ||
!moduleConfig.canned_message.enabled) {
powerFSM.trigger(EVENT_PRESS);
}
#endif
#if defined(USERPREFS_BUTTON_PIN)
if (((config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN) !=
moduleConfig.canned_message.inputbroker_pin_press) ||
!(moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.rotary1_enabled) ||
!moduleConfig.canned_message.enabled) {
powerFSM.trigger(EVENT_PRESS);
}
#endif
#endif
#if defined(ARCH_PORTDUINO)
if ((settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) &&
(settingsMap[user] != moduleConfig.canned_message.inputbroker_pin_press) ||
!moduleConfig.canned_message.enabled) {
powerFSM.trigger(EVENT_PRESS);
}
#endif
}
void ButtonThread::sendAdHocPosition()
{
service->refreshLocalMeshNode();
auto sentPosition = service->trySendPosition(NODENUM_BROADCAST, true);
if (screen) {
if (sentPosition)
screen->print("Sent ad-hoc position\n");
else
screen->print("Sent ad-hoc nodeinfo\n");
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
}
}
int32_t ButtonThread::runOnce()
{
// If the button is pressed we suppress CPU sleep until release
canSleep = true; // Assume we should not keep the board awake
#if defined(BUTTON_PIN) || defined(USERPREFS_BUTTON_PIN)
userButton.tick();
canSleep &= userButton.isIdle();
#elif defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
userButton.tick();
canSleep &= userButton.isIdle();
}
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt.tick();
canSleep &= userButtonAlt.isIdle();
#endif
#ifdef BUTTON_PIN_TOUCH
userButtonTouch.tick();
canSleep &= userButtonTouch.isIdle();
#endif
if (btnEvent != BUTTON_EVENT_NONE) {
switch (btnEvent) {
case BUTTON_EVENT_PRESSED: {
LOG_BUTTON("press!");
// If a nag notification is running, stop it and prevent other actions
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
externalNotificationModule->stopNow();
break;
}
#ifdef ELECROW_ThinkNode_M1
sendAdHocPosition();
break;
#endif
switchPage();
break;
}
case BUTTON_EVENT_PRESSED_SCREEN: {
LOG_BUTTON("AltPress!");
#ifdef ELECROW_ThinkNode_M1
// If a nag notification is running, stop it and prevent other actions
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
externalNotificationModule->stopNow();
break;
}
switchPage();
break;
#endif
// turn screen on or off
screen_flag = !screen_flag;
if (screen)
screen->setOn(screen_flag);
break;
}
case BUTTON_EVENT_DOUBLE_PRESSED: {
LOG_BUTTON("Double press!");
#ifdef ELECROW_ThinkNode_M1
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
break;
#endif
sendAdHocPosition();
break;
}
case BUTTON_EVENT_MULTI_PRESSED: {
LOG_BUTTON("Mulitipress! %hux", multipressClickCount);
switch (multipressClickCount) {
#if HAS_GPS && !defined(ELECROW_ThinkNode_M1)
// 3 clicks: toggle GPS
case 3:
if (!config.device.disable_triple_click && (gps != nullptr)) {
gps->toggleGpsMode();
if (screen)
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
}
break;
#elif defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
case 3:
LOG_INFO("3 clicks: toggle buzzer");
buzzer_flag = !buzzer_flag;
if (!buzzer_flag)
noTone(PIN_BUZZER);
break;
#endif
#if defined(USE_EINK) && defined(PIN_EINK_EN) && !defined(ELECROW_ThinkNode_M1) // i.e. T-Echo
// 4 clicks: toggle backlight
case 4:
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
break;
#endif
#if defined(RAK_4631)
// 5 clicks: start accelerometer/magenetometer calibration for 30 seconds
case 5:
if (accelerometerThread) {
accelerometerThread->calibrate(30);
}
break;
// 6 clicks: start accelerometer/magenetometer calibration for 60 seconds
case 6:
if (accelerometerThread) {
accelerometerThread->calibrate(60);
}
break;
#endif
// No valid multipress action
default:
break;
} // end switch: click count
break;
} // end multipress event
case BUTTON_EVENT_LONG_PRESSED: {
LOG_BUTTON("Long press!");
powerFSM.trigger(EVENT_PRESS);
if (screen) {
screen->startAlert("Shutting down...");
}
playBeep();
break;
}
// Do actual shutdown when button released, otherwise the button release
// may wake the board immediatedly.
case BUTTON_EVENT_LONG_RELEASED: {
LOG_INFO("Shutdown from long press");
playShutdownMelody();
delay(3000);
power->shutdown();
break;
}
#ifdef BUTTON_PIN_TOUCH
case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
LOG_BUTTON("Touch press!");
if (screen) {
// Wake if asleep
if (powerFSM.getState() == &stateDARK)
powerFSM.trigger(EVENT_PRESS);
// Update display (legacy behaviour)
screen->forceDisplay();
}
break;
}
#endif // BUTTON_PIN_TOUCH
default:
break;
}
btnEvent = BUTTON_EVENT_NONE;
}
return 50;
}
/*
* Attach (or re-attach) hardware interrupts for buttons
* Public method. Used outside class when waking from MCU sleep
*/
void ButtonThread::attachButtonInterrupts()
{
#if defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
wakeOnIrq(settingsMap[user], FALLING);
#elif defined(BUTTON_PIN)
// Interrupt for user button, during normal use. Improves responsiveness.
attachInterrupt(
#if !defined(USERPREFS_BUTTON_PIN)
config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN,
#endif
#if defined(USERPREFS_BUTTON_PIN)
config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN,
#endif
[]() {
ButtonThread::userButton.tick();
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
},
CHANGE);
#endif
#ifdef BUTTON_PIN_ALT
#ifdef ELECROW_ThinkNode_M2
wakeOnIrq(BUTTON_PIN_ALT, RISING);
#else
wakeOnIrq(BUTTON_PIN_ALT, FALLING);
#endif
#endif
#ifdef BUTTON_PIN_TOUCH
wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
#endif
}
/*
* Detach the "normal" button interrupts.
* Public method. Used before attaching a "wake-on-button" interrupt for MCU sleep
*/
void ButtonThread::detachButtonInterrupts()
{
#if defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
detachInterrupt(settingsMap[user]);
#elif defined(BUTTON_PIN)
#if !defined(USERPREFS_BUTTON_PIN)
detachInterrupt(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
#endif
#if defined(USERPREFS_BUTTON_PIN)
detachInterrupt(config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN);
#endif
#endif
#ifdef BUTTON_PIN_ALT
detachInterrupt(BUTTON_PIN_ALT);
#endif
#ifdef BUTTON_PIN_TOUCH
detachInterrupt(BUTTON_PIN_TOUCH);
#endif
}
#ifdef ARCH_ESP32
// Detach our class' interrupts before lightsleep
// Allows sleep.cpp to configure its own interrupts, which wake the device on user-button press
int ButtonThread::beforeLightSleep(void *unused)
{
detachButtonInterrupts();
return 0; // Indicates success
}
// Reconfigure our interrupts
// Our class' interrupts were disconnected during sleep, to allow the user button to wake the device from sleep
int ButtonThread::afterLightSleep(esp_sleep_wakeup_cause_t cause)
{
attachButtonInterrupts();
return 0; // Indicates success
}
#endif
/**
* Watch a GPIO and if we get an IRQ, wake the main thread.
* Use to add wake on button press
*/
void ButtonThread::wakeOnIrq(int irq, int mode)
{
attachInterrupt(
irq,
[] {
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
runASAP = true;
},
FALLING);
}
// Static callback
void ButtonThread::userButtonMultiPressed(void *callerThread)
{
// Grab click count from non-static button, while the info is still valid
ButtonThread *thread = (ButtonThread *)callerThread;
thread->storeClickCount();
// Then handle later, in the usual way
btnEvent = BUTTON_EVENT_MULTI_PRESSED;
}
// Non-static method, runs during callback. Grabs info while still valid
void ButtonThread::storeClickCount()
{
#if defined(BUTTON_PIN) || defined(USERPREFS_BUTTON_PIN)
multipressClickCount = userButton.getNumberClicks();
#endif
}
void ButtonThread::userButtonPressedLongStart()
{
if (millis() > c_holdOffTime) {
btnEvent = BUTTON_EVENT_LONG_PRESSED;
}
}
void ButtonThread::userButtonPressedLongStop()
{
if (millis() > c_holdOffTime) {
btnEvent = BUTTON_EVENT_LONG_RELEASED;
}
}

View File

@@ -1,91 +0,0 @@
#pragma once
#include "OneButton.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#ifndef BUTTON_CLICK_MS
#define BUTTON_CLICK_MS 250
#endif
#ifndef BUTTON_LONGPRESS_MS
#define BUTTON_LONGPRESS_MS 5000
#endif
#ifndef BUTTON_TOUCH_MS
#define BUTTON_TOUCH_MS 400
#endif
class ButtonThread : public concurrency::OSThread
{
public:
static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
enum ButtonEventType {
BUTTON_EVENT_NONE,
BUTTON_EVENT_PRESSED,
BUTTON_EVENT_PRESSED_SCREEN,
BUTTON_EVENT_DOUBLE_PRESSED,
BUTTON_EVENT_MULTI_PRESSED,
BUTTON_EVENT_LONG_PRESSED,
BUTTON_EVENT_LONG_RELEASED,
BUTTON_EVENT_TOUCH_LONG_PRESSED,
};
ButtonThread();
int32_t runOnce() override;
void attachButtonInterrupts();
void detachButtonInterrupts();
void storeClickCount();
bool isBuzzing() { return buzzer_flag; }
void setScreenFlag(bool flag) { screen_flag = flag; }
bool getScreenFlag() { return screen_flag; }
// Disconnect and reconnect interrupts for light sleep
#ifdef ARCH_ESP32
int beforeLightSleep(void *unused);
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
#endif
private:
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN)
static OneButton userButton; // Static - accessed from an interrupt
#endif
#ifdef BUTTON_PIN_ALT
OneButton userButtonAlt;
#endif
#ifdef BUTTON_PIN_TOUCH
OneButton userButtonTouch;
#endif
#ifdef ARCH_ESP32
// Get notified when lightsleep begins and ends
CallbackObserver<ButtonThread, void *> lsObserver =
CallbackObserver<ButtonThread, void *>(this, &ButtonThread::beforeLightSleep);
CallbackObserver<ButtonThread, esp_sleep_wakeup_cause_t> lsEndObserver =
CallbackObserver<ButtonThread, esp_sleep_wakeup_cause_t>(this, &ButtonThread::afterLightSleep);
#endif
// set during IRQ
static volatile ButtonEventType btnEvent;
bool buzzer_flag = false;
bool screen_flag = true;
// Store click count during callback, for later use
volatile int multipressClickCount = 0;
static void wakeOnIrq(int irq, int mode);
static void sendAdHocPosition();
static void switchPage();
// IRQ callbacks
static void userButtonPressed() { btnEvent = BUTTON_EVENT_PRESSED; }
static void userButtonPressedScreen() { btnEvent = BUTTON_EVENT_PRESSED_SCREEN; }
static void userButtonDoublePressed() { btnEvent = BUTTON_EVENT_DOUBLE_PRESSED; }
static void userButtonMultiPressed(void *callerThread); // Retrieve click count from non-static Onebutton while still valid
static void userButtonPressedLongStart();
static void userButtonPressedLongStop();
static void touchPressedLongStart() { btnEvent = BUTTON_EVENT_TOUCH_LONG_PRESSED; }
};
extern ButtonThread *buttonThread;

View File

@@ -103,7 +103,7 @@ NullSensor ina3221Sensor;
#endif
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_STM32WL)
#if !MESHTASTIC_EXCLUDE_I2C
#include "modules/Telemetry/Sensor/MAX17048Sensor.h"
#include <utility>
extern std::pair<uint8_t, TwoWire *> nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1];
@@ -278,7 +278,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
}
#endif
#if HAS_TELEMETRY && !defined(ARCH_STM32WL) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#if HAS_TELEMETRY && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (hasINA()) {
return getINAVoltage();
}
@@ -456,8 +456,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
#ifdef EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#else
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_STM32WL) && \
!defined(DISABLE_INA_CHARGING_DETECTION)
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
if (hasINA()) {
// get current flow from INA sensor - negative value means power flowing into the battery
// default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD
@@ -503,7 +502,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
}
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_STM32WL)
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
uint16_t getINAVoltage()
{
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
@@ -661,12 +660,14 @@ bool Power::analogInit()
*/
bool Power::setup()
{
// initialise one power sensor (only)
bool found = axpChipInit();
if (!found)
found = lipoInit();
if (!found)
found = analogInit();
bool found = false;
if (axpChipInit()) {
found = true;
} else if (lipoInit()) {
found = true;
} else if (analogInit()) {
found = true;
}
#ifdef NRF_APM
found = true;
@@ -680,7 +681,7 @@ bool Power::setup()
void Power::shutdown()
{
LOG_INFO("Shutting down");
LOG_INFO("Shutting Down");
#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
#ifdef PIN_LED1
@@ -853,7 +854,8 @@ int32_t Power::runOnce()
#ifndef T_WATCH_S3 // FIXME - why is this triggering on the T-Watch S3?
if (PMU->isPekeyLongPressIrq()) {
LOG_DEBUG("PEK long button press");
screen->setOn(false);
if (screen)
screen->setOn(false);
}
#endif
@@ -1158,7 +1160,7 @@ bool Power::axpChipInit()
#endif
}
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
#if !MESHTASTIC_EXCLUDE_I2C && __has_include(<Adafruit_MAX1704X.h>)
/**
* Wrapper class for an I2C MAX17048 Lipo battery sensor.

View File

@@ -26,7 +26,7 @@
#ifndef SLEEP_TIME
#define SLEEP_TIME 30
#endif
#if EXCLUDE_POWER_FSM
#if MESHTASTIC_EXCLUDE_POWER_FSM
FakeFsm powerFSM;
void PowerFSM_setup(){};
#else
@@ -82,7 +82,8 @@ static uint32_t secsSlept;
static void lsEnter()
{
LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs);
screen->setOn(false);
if (screen)
screen->setOn(false);
secsSlept = 0; // How long have we been sleeping this time
// LOG_INFO("lsEnter end");
@@ -160,7 +161,8 @@ static void lsExit()
static void nbEnter()
{
LOG_DEBUG("State: NB");
screen->setOn(false);
if (screen)
screen->setOn(false);
#ifdef ARCH_ESP32
// Only ESP32 should turn off bluetooth
setBluetoothEnable(false);
@@ -172,22 +174,23 @@ static void nbEnter()
static void darkEnter()
{
setBluetoothEnable(true);
screen->setOn(false);
if (screen)
screen->setOn(false);
}
static void serialEnter()
{
LOG_DEBUG("State: SERIAL");
setBluetoothEnable(false);
screen->setOn(true);
screen->print("Serial connected\n");
if (screen) {
screen->setOn(true);
}
}
static void serialExit()
{
// Turn bluetooth back on when we leave serial stream API
setBluetoothEnable(true);
screen->print("Serial disconnected\n");
}
static void powerEnter()
@@ -198,15 +201,10 @@ static void powerEnter()
LOG_INFO("Loss of power in Powered");
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
} else {
screen->setOn(true);
if (screen)
screen->setOn(true);
setBluetoothEnable(true);
// within enter() the function getState() returns the state we came from
// Mothballed: print change of power-state to device screen
/* if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 &&
strcmp(powerFSM.getState()->name, "DARK") != 0) {
screen->print("Powered...\n");
}*/
}
}
@@ -221,18 +219,16 @@ static void powerIdle()
static void powerExit()
{
screen->setOn(true);
if (screen)
screen->setOn(true);
setBluetoothEnable(true);
// Mothballed: print change of power-state to device screen
/*if (!isPowered())
screen->print("Unpowered...\n");*/
}
static void onEnter()
{
LOG_DEBUG("State: ON");
screen->setOn(true);
if (screen)
screen->setOn(true);
setBluetoothEnable(true);
}
@@ -244,11 +240,6 @@ static void onIdle()
}
}
static void screenPress()
{
screen->onPress();
}
static void bootEnter()
{
LOG_DEBUG("State: BOOT");
@@ -292,9 +283,9 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
powerFSM.add_transition(&stateDARK, isPowered() ? &statePOWER : &stateON, EVENT_PRESS, NULL, "Press");
powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_PRESS, screenPress, "Press");
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
powerFSM.add_transition(&stateSERIAL, &stateSERIAL, EVENT_PRESS, screenPress,
powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_PRESS, NULL, "Press");
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, NULL, "Press"); // reenter On to restart our timers
powerFSM.add_transition(&stateSERIAL, &stateSERIAL, EVENT_PRESS, NULL,
"Press"); // Allow button to work while in serial API
// Handle critically low power battery by forcing deep sleep
@@ -328,10 +319,10 @@ void PowerFSM_setup()
// if any packet destined for phone arrives, turn on bluetooth at least
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
// show the latest node when we get a new node db update
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
// Removed 2.7: we don't show the nodes individually for every node on the screen anymore
// powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
// powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
// powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
// Show the received text message
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");

View File

@@ -11,7 +11,7 @@
#define EVENT_RECEIVED_MSG 5
// #define EVENT_BOOT 6 // now done with a timed transition
#define EVENT_BLUETOOTH_PAIR 7
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
// #define EVENT_NODEDB_UPDATED 8 // Now defunct: NodeDB has a big enough change that we think you should turn on the screen
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep
#define EVENT_SERIAL_CONNECTED 11
@@ -22,7 +22,7 @@
#define EVENT_SHUTDOWN 16 // force a full shutdown now (not just sleep)
#define EVENT_INPUT 17 // input broker wants something, we need to wake up and enable screen
#if EXCLUDE_POWER_FSM
#if MESHTASTIC_EXCLUDE_POWER_FSM
class FakeFsm
{
public:

View File

@@ -18,7 +18,7 @@ class PowerFSMThread : public OSThread
protected:
int32_t runOnce() override
{
#if !EXCLUDE_POWER_FSM
#if !MESHTASTIC_EXCLUDE_POWER_FSM
powerFSM.run_machine();
/// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake

View File

@@ -352,8 +352,8 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
for (uint16_t i = 0; i < len; i += 16) {
if (i % 128 == 0)
log(logLevel, " +------------------------------------------------+ +----------------+");
char s[] = "| | | |\n";
uint8_t ix = 1, iy = 52;
char s[] = " | | | |\n";
uint8_t ix = 5, iy = 56;
for (uint8_t j = 0; j < 16; j++) {
if (i + j < len) {
uint8_t c = buf[i + j];
@@ -367,10 +367,8 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
}
}
uint8_t index = i / 16;
if (i < 256)
log(logLevel, " ");
log(logLevel, "%02x", index);
log(logLevel, ".");
sprintf(s, "%03x", index);
s[3] = '.';
log(logLevel, s);
}
log(logLevel, " +------------------------------------------------+ +----------------+");
@@ -393,4 +391,4 @@ std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
break;
}
return std::string(formatted.get());
}
}

View File

@@ -0,0 +1,79 @@
#include "BuzzerFeedbackThread.h"
#include "NodeDB.h"
#include "buzz.h"
#include "configuration.h"
BuzzerFeedbackThread *buzzerFeedbackThread;
BuzzerFeedbackThread::BuzzerFeedbackThread() : OSThread("BuzzerFeedback")
{
if (inputBroker)
inputObserver.observe(inputBroker);
}
int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
{
// Only provide feedback if buzzer is enabled for notifications
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY) {
return 0; // Let other handlers process the event
}
// Track last event time for potential future use
lastEventTime = millis();
needsUpdate = true;
// Handle different input events with appropriate buzzer feedback
switch (event->inputEvent) {
case INPUT_BROKER_USER_PRESS:
case INPUT_BROKER_ALT_PRESS:
case INPUT_BROKER_SELECT:
playBeep(); // Confirmation feedback
break;
case INPUT_BROKER_UP:
case INPUT_BROKER_DOWN:
case INPUT_BROKER_LEFT:
case INPUT_BROKER_RIGHT:
playChirp(); // Navigation feedback
break;
case INPUT_BROKER_CANCEL:
case INPUT_BROKER_BACK:
playBoop(); // Cancel/back feedback
break;
case INPUT_BROKER_SEND_PING:
playComboTune(); // Ping sent feedback
break;
case INPUT_BROKER_SHUTDOWN:
playShutdownMelody(); // Shutdown feedback
break;
default:
// For other events, check if it's a printable character
if (event->kbchar >= 32 && event->kbchar <= 126) {
// Typing feedback - very short boop
// Removing this for now, too chatty
// playChirp();
}
break;
}
return 0; // Allow other handlers to process the event
}
int32_t BuzzerFeedbackThread::runOnce()
{
// This thread is primarily event-driven, but we can use runOnce
// for any periodic tasks if needed in the future
if (needsUpdate) {
needsUpdate = false;
// Could add any periodic processing here
}
// Run every 100ms when active, less frequently when idle
return needsUpdate ? 100 : 1000;
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "Observer.h"
#include "concurrency/OSThread.h"
#include "input/InputBroker.h"
class BuzzerFeedbackThread : public concurrency::OSThread
{
CallbackObserver<BuzzerFeedbackThread, const InputEvent *> inputObserver =
CallbackObserver<BuzzerFeedbackThread, const InputEvent *>(this, &BuzzerFeedbackThread::handleInputEvent);
public:
BuzzerFeedbackThread();
int handleInputEvent(const InputEvent *event);
protected:
virtual int32_t runOnce() override;
private:
uint32_t lastEventTime = 0;
bool needsUpdate = false;
};
extern BuzzerFeedbackThread *buzzerFeedbackThread;

View File

@@ -38,6 +38,11 @@ const int DURATION_1_1 = 1000; // 1/1 note
void playTones(const ToneDuration *tone_durations, int size)
{
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY) {
// Buzzer is disabled or not set to system tones
return;
}
#ifdef PIN_BUZZER
if (!config.device.buzzer_gpio)
config.device.buzzer_gpio = PIN_BUZZER;
@@ -54,7 +59,7 @@ void playTones(const ToneDuration *tone_durations, int size)
void playBeep()
{
ToneDuration melody[] = {{NOTE_B3, DURATION_1_4}};
ToneDuration melody[] = {{NOTE_B3, DURATION_1_8}};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
@@ -87,3 +92,72 @@ void playShutdownMelody()
ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8}, {NOTE_AS3, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void playChirp()
{
// A short, friendly "chirp" sound for key presses
ToneDuration melody[] = {{NOTE_AS3, 20}}; // Very short AS3 note
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void playBoop()
{
// A short, friendly "boop" sound for button presses
ToneDuration melody[] = {{NOTE_A3, 50}}; // Very short A3 note
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void playLongPressLeadUp()
{
// An ascending lead-up sequence for long press - builds anticipation
ToneDuration melody[] = {
{NOTE_C3, 100}, // Start low
{NOTE_E3, 100}, // Step up
{NOTE_G3, 100}, // Keep climbing
{NOTE_B3, 150} // Peak with longer note for emphasis
};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
// Static state for progressive lead-up notes
static int leadUpNoteIndex = 0;
static const ToneDuration leadUpNotes[] = {
{NOTE_C3, 100}, // Start low
{NOTE_E3, 100}, // Step up
{NOTE_G3, 100}, // Keep climbing
{NOTE_B3, 150} // Peak with longer note for emphasis
};
static const int leadUpNotesCount = sizeof(leadUpNotes) / sizeof(ToneDuration);
bool playNextLeadUpNote()
{
if (leadUpNoteIndex >= leadUpNotesCount) {
return false; // All notes have been played
}
// Use playTones to handle buzzer logic consistently
const auto &note = leadUpNotes[leadUpNoteIndex];
playTones(&note, 1); // Play single note using existing playTones function
leadUpNoteIndex++;
return true; // Note was played (playTones handles buzzer availability internally)
}
void resetLeadUpSequence()
{
leadUpNoteIndex = 0;
}
void playComboTune()
{
// Quick high-pitched notes with trills
ToneDuration melody[] = {
{NOTE_G3, 80}, // Quick chirp
{NOTE_B3, 60}, // Higher chirp
{NOTE_CS4, 80}, // Even higher
{NOTE_G3, 60}, // Quick trill down
{NOTE_CS4, 60}, // Quick trill up
{NOTE_B3, 120} // Ending chirp
};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}

View File

@@ -5,4 +5,10 @@ void playLongBeep();
void playStartMelody();
void playShutdownMelody();
void playGPSEnableBeep();
void playGPSDisableBeep();
void playGPSDisableBeep();
void playComboTune();
void playBoop();
void playChirp();
void playLongPressLeadUp();
bool playNextLeadUpNote(); // Play the next note in the lead-up sequence
void resetLeadUpSequence(); // Reset the lead-up sequence to start from beginning

View File

@@ -12,7 +12,7 @@ enum class Cmd {
STOP_ALERT_FRAME,
START_FIRMWARE_UPDATE_SCREEN,
STOP_BOOT_SCREEN,
PRINT,
SHOW_PREV_FRAME,
SHOW_NEXT_FRAME
SHOW_NEXT_FRAME,
NOOP
};

View File

@@ -9,17 +9,23 @@ namespace concurrency
Lock::Lock() : handle(xSemaphoreCreateBinary())
{
assert(handle);
assert(xSemaphoreGive(handle));
if (xSemaphoreGive(handle) == false) {
abort();
}
}
void Lock::lock()
{
assert(xSemaphoreTake(handle, portMAX_DELAY));
if (xSemaphoreTake(handle, portMAX_DELAY) == false) {
abort();
}
}
void Lock::unlock()
{
assert(xSemaphoreGive(handle));
if (xSemaphoreGive(handle) == false) {
abort();
}
}
#else
Lock::Lock() {}

View File

@@ -80,10 +80,46 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Override user saved region, for producing region-locked builds
// #define REGULATORY_LORA_REGIONCODE meshtastic_Config_LoRaConfig_RegionCode_SG_923
// Total system gain in dBm to subtract from Tx power to remain within regulatory ERP limit for non-licensed operators
// This value should be set in variant.h and is PA gain + antenna gain (if system ships with an antenna)
#ifndef REGULATORY_GAIN_LORA
#define REGULATORY_GAIN_LORA 0
// Total system gain in dBm to subtract from Tx power to remain within regulatory and Tx PA limits
// The value consists of PA gain + antenna gain (if variant has a non-removable antenna)
// TX_GAIN_LORA should be set with definitions below for common modules, or in variant.h.
// Gain for common modules with transmit PAs
#ifdef EBYTE_E22_900M30S
// 10dB PA gain and 30dB rated output; based on measurements from
// https://github.com/S5NC/EBYTE_ESP32-S3/blob/main/E22-900M30S%20power%20output%20testing.txt
#define TX_GAIN_LORA 7
#define SX126X_MAX_POWER 22
#endif
#ifdef EBYTE_E22_900M33S
// 25dB PA gain and 33dB rated output; based on TX Power Curve from E22-900M33S_UserManual_EN_v1.0.pdf
#define TX_GAIN_LORA 25
#define SX126X_MAX_POWER 8
#endif
#ifdef NICERF_MINIF27
// Note that datasheet power level of 9 corresponds with SX1262 at 22dBm
// Maximum output power of 29dBm with VCC_PA = 5V
#define TX_GAIN_LORA 7
#define SX126X_MAX_POWER 22
#endif
#ifdef NICERF_F30_HF
// Maximum output power of 29.6dBm with VCC = 5V and SX1262 at 22dBm
#define TX_GAIN_LORA 8
#define SX126X_MAX_POWER 22
#endif
#ifdef NICERF_F30_LF
// Maximum output power of 32.0dBm with VCC = 5V and SX1262 at 22dBm
#define TX_GAIN_LORA 10
#define SX126X_MAX_POWER 22
#endif
// Default system gain to 0 if not defined
#ifndef TX_GAIN_LORA
#define TX_GAIN_LORA 0
#endif
// -----------------------------------------------------------------------------
@@ -99,8 +135,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------------
// OLED & Input
// -----------------------------------------------------------------------------
#if defined(SEEED_WIO_TRACKER_L1)
#define SSD1306_ADDRESS 0x3D
#define USE_SH1106
#else
#define SSD1306_ADDRESS 0x3C
#endif
#define ST7567_ADDRESS 0x3F
// The SH1106 controller is almost, but not quite, the same as SSD1306
@@ -149,10 +189,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define DFROBOT_RAIN_ADDR 0x1d
#define NAU7802_ADDR 0x2A
#define MAX30102_ADDR 0x57
#define SCD4X_ADDR 0x62
#define MLX90614_ADDR_DEF 0x5A
#define CGRADSENS_ADDR 0x66
#define LTR390UV_ADDR 0x53
#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418
#define PCT2075_ADDR 0x37
// -----------------------------------------------------------------------------
// ACCELEROMETER
@@ -166,6 +208,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BMX160_ADDR 0x69
#define ICM20948_ADDR 0x69
#define ICM20948_ADDR_ALT 0x68
#define BMM150_ADDR 0x13
// -----------------------------------------------------------------------------
// LED
@@ -188,6 +231,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------------
#define FT6336U_ADDR 0x48
// -----------------------------------------------------------------------------
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
// - the default i2c address for this sensor is 0x20, and users are instructed to
// set 0x21 and 0x22 for the second and third sensor if present.
// -----------------------------------------------------------------------------
#define RAK120351_ADDR 0x20
#define RAK120352_ADDR 0x21
#define RAK120353_ADDR 0x22
// -----------------------------------------------------------------------------
// BIAS-T Generator
// -----------------------------------------------------------------------------
@@ -297,11 +349,41 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#error HW_VENDOR must be defined
#endif
#ifndef TB_DOWN
#define TB_DOWN 255
#endif
#ifndef TB_UP
#define TB_UP 255
#endif
#ifndef TB_LEFT
#define TB_LEFT 255
#endif
#ifndef TB_RIGHT
#define TB_RIGHT 255
#endif
#ifndef TB_PRESS
#define TB_PRESS 255
#endif
// Support multiple RGB LED configuration
#if defined(HAS_NCP5623) || defined(HAS_LP5562) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
#define HAS_RGB_LED
#endif
// default mapping of pins
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
#define ALT_BUTTON_PIN PIN_BUTTON2
#endif
#if defined ALT_BUTTON_PIN
#ifndef ALT_BUTTON_ACTIVE_LOW
#define ALT_BUTTON_ACTIVE_LOW true
#endif
#ifndef ALT_BUTTON_ACTIVE_PULLUP
#define ALT_BUTTON_ACTIVE_PULLUP true
#endif
#endif
// -----------------------------------------------------------------------------
// Global switches to turn off features for a minimized build
// -----------------------------------------------------------------------------

View File

@@ -37,8 +37,14 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P};
return firstOfOrNONE(8, types);
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150};
return firstOfOrNONE(9, types);
}
ScanI2C::FoundDevice ScanI2C::firstAQI() const
{
ScanI2C::DeviceType types[] = {PMSA0031, SCD4X};
return firstOfOrNONE(2, types);
}
ScanI2C::FoundDevice ScanI2C::firstRGBLED() const
@@ -80,4 +86,4 @@ bool ScanI2C::DeviceAddress::operator<(const ScanI2C::DeviceAddress &other) cons
|| (port != NO_I2C && other.port != NO_I2C && (address < other.address));
}
ScanI2C::FoundDevice::FoundDevice(ScanI2C::DeviceType type, ScanI2C::DeviceAddress address) : type(type), address(address) {}
ScanI2C::FoundDevice::FoundDevice(ScanI2C::DeviceType type, ScanI2C::DeviceAddress address) : type(type), address(address) {}

View File

@@ -61,6 +61,7 @@ class ScanI2C
FT6336U,
STK8BAXX,
ICM20948,
SCD4X,
MAX30102,
TPS65233,
MPR121KB,
@@ -70,7 +71,10 @@ class ScanI2C
DFROBOT_RAIN,
DPS310,
LTR390UV,
RAK12035,
TCA8418KB,
PCT2075,
BMM150,
} DeviceType;
// typedef uint8_t DeviceAddress;
@@ -123,6 +127,8 @@ class ScanI2C
FoundDevice firstAccelerometer() const;
FoundDevice firstAQI() const;
FoundDevice firstRGBLED() const;
virtual FoundDevice find(DeviceType) const;

View File

@@ -358,7 +358,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) {
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c || registerValue == 0xc8d) {
type = SHT4X;
logFoundDevice("SHT4X", (uint8_t)addr.address);
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
@@ -423,9 +423,21 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("BMA423", (uint8_t)addr.address);
}
break;
case TCA9535_ADDR:
case RAK120352_ADDR:
case RAK120353_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x02), 1);
if (registerValue == addr.address) { // RAK12035 returns its I2C address at 0x02 (eg 0x20)
type = RAK12035;
logFoundDevice("RAK12035", (uint8_t)addr.address);
} else {
type = TCA9535;
logFoundDevice("TCA9535", (uint8_t)addr.address);
}
break;
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(TCA9535_ADDR, TCA9535, "TCA9535", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591", (uint8_t)addr.address);
@@ -434,6 +446,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (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(PCT2075_ADDR, PCT2075, "PCT2075", (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);
#ifdef HAS_TPS65233
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
#endif
@@ -542,4 +557,4 @@ void ScanI2CTwoWire::logFoundDevice(const char *device, uint8_t address)
{
LOG_INFO("%s found at address 0x%x", device, address);
}
#endif
#endif

View File

@@ -40,7 +40,9 @@ template <typename T, std::size_t N> std::size_t array_count(const T (&)[N])
}
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if defined(RAK2560)
#if defined(SENSECAP_INDICATOR)
FakeUART *GPS::_serial_gps = FakeSerial;
#elif defined(RAK2560)
HardwareSerial *GPS::_serial_gps = &Serial2;
#else
HardwareSerial *GPS::_serial_gps = &Serial1;
@@ -1076,6 +1078,7 @@ void GPS::publishUpdate()
int32_t GPS::runOnce()
{
#if !defined(SENSECAP_INDICATOR)
if (!GPSInitFinished) {
if (!_serial_gps || config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
LOG_INFO("GPS set to not-present. Skip probe");
@@ -1091,6 +1094,7 @@ int32_t GPS::runOnce()
GPSInitFinished = true;
publishUpdate();
}
#endif
// Repeaters have no need for GPS
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
@@ -1250,11 +1254,10 @@ GnssModel_t GPS::probe(int serialSpeed)
// Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
delay(20);
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B},
{"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S},
{"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
{"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}};
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
{"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B},
{"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
@@ -1410,8 +1413,10 @@ GPS *GPS::createGps()
if (!settingsMap[has_gps])
return nullptr;
#endif
#if !defined(SENSECAP_INDICATOR)
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
return nullptr;
#endif
GPS *new_gps = new GPS;
new_gps->rx_gpio = _rx_gpio;

View File

@@ -11,6 +11,10 @@
#include "input/UpDownInterruptImpl1.h"
#include "modules/PositionModule.h"
#ifdef SENSECAP_INDICATOR
#include "mesh/comms/FakeUART.h"
#endif
// Allow defining the polarity of the ENABLE output. default is active high
#ifndef GPS_EN_ACTIVE
#define GPS_EN_ACTIVE 1
@@ -188,7 +192,9 @@ class GPS : private concurrency::OSThread
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
/** If !NULL we will use this serial port to construct our GPS */
#if defined(ARCH_RP2040)
#if defined(SENSECAP_INDICATOR)
static FakeUART *_serial_gps;
#elif defined(ARCH_RP2040)
static SerialUART *_serial_gps;
#else
static HardwareSerial *_serial_gps;

View File

@@ -244,11 +244,15 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
*/
int32_t getTZOffset()
{
#if MESHTASTIC_EXCLUDE_TZ
return 0;
#else
time_t now = getTime(false);
struct tm *gmt;
gmt = gmtime(&now);
gmt->tm_isdst = -1;
return (int32_t)difftime(now, mktime(gmt));
#endif
}
/**

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,26 @@
#include "detect/ScanI2C.h"
#include "mesh/generated/meshtastic/config.pb.h"
#include <OLEDDisplay.h>
#include <functional>
#include <string>
#include <vector>
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
namespace graphics
{
enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker };
struct BannerOverlayOptions {
const char *message;
uint32_t durationMs = 30000;
const char **optionsArrayPtr = nullptr;
const int *optionsEnumPtr = nullptr;
uint8_t optionsCount = 0;
std::function<void(int)> bannerCallback = nullptr;
int8_t InitialSelected = 0;
notificationTypeEnum notificationType = notificationTypeEnum::text_banner;
};
} // namespace graphics
#if !HAS_SCREEN
#include "power.h"
@@ -14,11 +34,20 @@ namespace graphics
class Screen
{
public:
enum FrameFocus : uint8_t {
FOCUS_DEFAULT, // No specific frame
FOCUS_PRESERVE, // Return to the previous frame
FOCUS_FAULT,
FOCUS_TEXTMESSAGE,
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
FOCUS_CLOCK,
FOCUS_SYSTEM,
};
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
void onPress() {}
void setup() {}
void setOn(bool) {}
void print(const char *) {}
void doDeepSleep() {}
void forceDisplay(bool forceUiUpdate = false) {}
void startFirmwareUpdateScreen() {}
@@ -27,6 +56,9 @@ class Screen
void setFunctionSymbol(std::string) {}
void removeFunctionSymbol(std::string) {}
void startAlert(const char *) {}
void showSimpleBanner(const char *message, uint32_t durationMs = 0) {}
void showOverlayBanner(BannerOverlayOptions) {}
void setFrames(FrameFocus focus) {}
void endAlert() {}
};
} // namespace graphics
@@ -62,8 +94,10 @@ class Screen
#include "concurrency/OSThread.h"
#include "input/InputBroker.h"
#include "mesh/MeshModule.h"
#include "modules/AdminModule.h"
#include "power.h"
#include <string>
#include <vector>
// 0 to 255, though particular variants might define different defaults
#ifndef BRIGHTNESS_DEFAULT
@@ -90,7 +124,7 @@ class Screen
/// Convert an integer GPS coords to a floating point
#define DegD(i) (i * 1e-7)
extern bool hasUnreadMessage;
namespace
{
/// A basic 2D point class for drawing
@@ -176,14 +210,31 @@ class Screen : public concurrency::OSThread
CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules
CallbackObserver<Screen, const InputEvent *> inputObserver =
CallbackObserver<Screen, const InputEvent *>(this, &Screen::handleInputEvent);
CallbackObserver<Screen, const meshtastic_AdminMessage *> adminMessageObserver =
CallbackObserver<Screen, const meshtastic_AdminMessage *>(this, &Screen::handleAdminMessage);
CallbackObserver<Screen, AdminModule_ObserverData *> adminMessageObserver =
CallbackObserver<Screen, AdminModule_ObserverData *>(this, &Screen::handleAdminMessage);
public:
OLEDDisplay *getDisplayDevice() { return dispdev; }
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
size_t frameCount = 0; // Total number of active frames
~Screen();
// Which frame we want to be displayed, after we regen the frameset by calling setFrames
enum FrameFocus : uint8_t {
FOCUS_DEFAULT, // No specific frame
FOCUS_PRESERVE, // Return to the previous frame
FOCUS_FAULT,
FOCUS_TEXTMESSAGE,
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
FOCUS_CLOCK,
FOCUS_SYSTEM,
};
// Regenerate the normal set of frames, focusing a specific frame if requested
// Call when a frame should be added / removed, or custom frames should be cleared
void setFrames(FrameFocus focus = FOCUS_DEFAULT);
std::vector<const uint8_t *> indicatorIcons; // Per-frame custom icon pointers
Screen(const Screen &) = delete;
Screen &operator=(const Screen &) = delete;
@@ -191,6 +242,12 @@ class Screen : public concurrency::OSThread
meshtastic_Config_DisplayConfig_OledType model;
OLEDDISPLAY_GEOMETRY geometry;
bool isOverlayBannerShowing();
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
// FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class
char ourId[5];
/// Initializes the UI, turns on the display, starts showing boot screen.
//
// Not thread safe - must be called before any other methods are called.
@@ -214,21 +271,9 @@ class Screen : public concurrency::OSThread
void blink();
void drawFrameText(OLEDDisplay *, OLEDDisplayUiState *, int16_t, int16_t, const char *);
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
// Draw north
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading);
static uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight);
float estimatedHeading(double lat, double lon);
void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian);
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields);
/// Handle button press, trackball or swipe action)
void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); }
void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); }
@@ -260,6 +305,12 @@ class Screen : public concurrency::OSThread
enqueueCmd(cmd);
}
void showSimpleBanner(const char *message, uint32_t durationMs = 0);
void showOverlayBanner(BannerOverlayOptions);
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback);
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
void startFirmwareUpdateScreen()
{
ScreenCmd cmd;
@@ -272,7 +323,7 @@ class Screen : public concurrency::OSThread
void setHeading(long _heading)
{
hasCompass = true;
compassHeading = _heading;
compassHeading = fmod(_heading, 360);
}
bool hasHeading() { return hasCompass; }
@@ -292,23 +343,12 @@ class Screen : public concurrency::OSThread
/// Stops showing the boot screen.
void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); }
/// Writes a string to the screen.
void print(const char *text)
void runNow()
{
ScreenCmd cmd;
cmd.cmd = Cmd::PRINT;
// TODO(girts): strdup() here is scary, but we can't use std::string as
// FreeRTOS queue is just dumbly copying memory contents. It would be
// nice if we had a queue that could copy objects by value.
cmd.print_text = strdup(text);
if (!enqueueCmd(cmd)) {
free(cmd.print_text);
}
setFastFramerate();
enqueueCmd(ScreenCmd{.cmd = Cmd::NOOP});
}
/// generates a very brief time delta display
std::string drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds);
/// Overrides the default utf8 character conversion, to replace empty space with question marks
static char customFontTableLookup(const uint8_t ch)
{
@@ -533,7 +573,7 @@ class Screen : public concurrency::OSThread
int handleTextMessage(const meshtastic_MeshPacket *arg);
int handleUIFrameEvent(const UIFrameEvent *arg);
int handleInputEvent(const InputEvent *arg);
int handleAdminMessage(const meshtastic_AdminMessage *arg);
int handleAdminMessage(AdminModule_ObserverData *arg);
/// Used to force (super slow) eink displays to draw critical frames
void forceDisplay(bool forceUiUpdate = false);
@@ -541,8 +581,6 @@ class Screen : public concurrency::OSThread
/// Draws our SSL cert screen during boot (called from WebServer)
void setSSLFrames();
void setWelcomeFrames();
// Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
void dismissCurrentFrame();
@@ -591,7 +629,6 @@ class Screen : public concurrency::OSThread
void handleOnPress();
void handleShowNextFrame();
void handleShowPrevFrame();
void handlePrint(const char *text);
void handleStartFirmwareUpdateScreen();
// Info collected by setFrames method.
@@ -600,30 +637,37 @@ class Screen : public concurrency::OSThread
// - Used to dismiss the currently shown frame (txt; waypoint) by CardKB combo
struct FramesetInfo {
struct FramePositions {
uint8_t fault = 0;
uint8_t textMessage = 0;
uint8_t waypoint = 0;
uint8_t focusedModule = 0;
uint8_t log = 0;
uint8_t settings = 0;
uint8_t wifi = 0;
uint8_t fault = 255;
uint8_t waypoint = 255;
uint8_t focusedModule = 255;
uint8_t log = 255;
uint8_t settings = 255;
uint8_t wifi = 255;
uint8_t deviceFocused = 255;
uint8_t memory = 255;
uint8_t gps = 255;
uint8_t home = 255;
uint8_t textMessage = 255;
uint8_t nodelist = 255;
uint8_t nodelist_lastheard = 255;
uint8_t nodelist_hopsignal = 255;
uint8_t nodelist_distance = 255;
uint8_t nodelist_bearings = 255;
uint8_t clock = 255;
uint8_t firstFavorite = 255;
uint8_t lastFavorite = 255;
uint8_t lora = 255;
} positions;
uint8_t frameCount = 0;
} framesetInfo;
// Which frame we want to be displayed, after we regen the frameset by calling setFrames
enum FrameFocus : uint8_t {
FOCUS_DEFAULT, // No specific frame
FOCUS_PRESERVE, // Return to the previous frame
FOCUS_FAULT,
FOCUS_TEXTMESSAGE,
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
};
// Regenerate the normal set of frames, focusing a specific frame if requested
// Call when a frame should be added / removed, or custom frames should be cleared
void setFrames(FrameFocus focus = FOCUS_DEFAULT);
struct DismissedFrames {
bool textMessage = false;
bool waypoint = false;
bool wifi = false;
bool memory = false;
} dismissedFrames;
/// Try to start drawing ASAP
void setFastFramerate();
@@ -631,34 +675,6 @@ class Screen : public concurrency::OSThread
// Sets frame up for immediate drawing
void setFrameImmediateDraw(FrameCallback *drawFrames);
/// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame.
static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
#if defined(DISPLAY_CLOCK_FRAME)
static void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale = 1);
static void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height);
static void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height);
static void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale = 1);
static void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
static void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
// Whether we are showing the digital watch face or the analog one
bool digitalWatchFace = true;
#endif
/// callback for current alert frame
FrameCallback alertFrame;
@@ -691,4 +707,9 @@ class Screen : public concurrency::OSThread
} // namespace graphics
// Extern declarations for function symbols used in UIRenderer
extern std::vector<std::string> functionSymbol;
extern std::string functionSymbolString;
extern graphics::Screen *screen;
#endif

View File

@@ -65,8 +65,8 @@
#endif
#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(DISPLAY_FORCE_SMALL_FONTS)
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
// The screen is bigger so use bigger fonts
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28

View File

@@ -0,0 +1,6 @@
#include <string>
#include <vector>
// Global variables for screen function overlay
std::vector<std::string> functionSymbol;
std::string functionSymbolString;

View File

@@ -0,0 +1,394 @@
#include "graphics/SharedUIDisplay.h"
#include "RTC.h"
#include "graphics/ScreenFonts.h"
#include "main.h"
#include "meshtastic/config.pb.h"
#include "power.h"
#include <OLEDDisplay.h>
#include <graphics/images.h>
namespace graphics
{
void determineResolution(int16_t screenheight, int16_t screenwidth)
{
if (screenwidth > 128) {
isHighResolution = true;
}
// Special case for Heltec Wireless Tracker v1.1
if (screenwidth == 160 && screenheight == 80) {
isHighResolution = false;
}
}
// === Shared External State ===
bool hasUnreadMessage = false;
bool isMuted = false;
bool isHighResolution = false;
// === Internal State ===
bool isBoltVisibleShared = true;
uint32_t lastBlinkShared = 0;
bool isMailIconVisible = true;
uint32_t lastMailBlink = 0;
// *********************************
// * Rounded Header when inverted *
// *********************************
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r)
{
// Draw the center and side rectangles
display->fillRect(x + r, y, w - 2 * r, h); // center bar
display->fillRect(x, y + r, r, h - 2 * r); // left edge
display->fillRect(x + w - r, y + r, r, h - 2 * r); // right edge
// Draw the rounded corners using filled circles
display->fillCircle(x + r + 1, y + r, r); // top-left
display->fillCircle(x + w - r - 1, y + r, r); // top-right
display->fillCircle(x + r + 1, y + h - r - 1, r); // bottom-left
display->fillCircle(x + w - r - 1, y + h - r - 1, r); // bottom-right
}
// *************************
// * Common Header Drawing *
// *************************
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only)
{
constexpr int HEADER_OFFSET_Y = 1;
y += HEADER_OFFSET_Y;
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const int xOffset = 4;
const int highlightHeight = FONT_HEIGHT_SMALL - 1;
const bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
const bool isBold = config.display.heading_bold;
const int screenW = display->getWidth();
const int screenH = display->getHeight();
if (!battery_only) {
// === Inverted Header Background ===
if (isInverted) {
display->setColor(BLACK);
display->fillRect(0, 0, screenW, highlightHeight + 2);
display->setColor(WHITE);
drawRoundedHighlight(display, x, y, screenW, highlightHeight, 2);
display->setColor(BLACK);
} else {
display->setColor(BLACK);
display->fillRect(0, 0, screenW, highlightHeight + 2);
display->setColor(WHITE);
if (isHighResolution) {
display->drawLine(0, 20, screenW, 20);
} else {
display->drawLine(0, 14, screenW, 14);
}
}
// === Screen Title ===
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(SCREEN_WIDTH / 2, y, titleStr);
if (config.display.heading_bold) {
display->drawString((SCREEN_WIDTH / 2) + 1, y, titleStr);
}
}
display->setTextAlignment(TEXT_ALIGN_LEFT);
// === Battery State ===
int chargePercent = powerStatus->getBatteryChargePercent();
bool isCharging = powerStatus->getIsCharging();
bool usbPowered = powerStatus->getHasUSB();
if (chargePercent >= 100) {
isCharging = false;
}
if (chargePercent == 101) {
usbPowered = true; // Forcing this flag on for the express purpose that some devices have no concept of having a USB cable
// plugged in
}
uint32_t now = millis();
#ifndef USE_EINK
if (isCharging && now - lastBlinkShared > 500) {
isBoltVisibleShared = !isBoltVisibleShared;
lastBlinkShared = now;
}
#endif
bool useHorizontalBattery = (isHighResolution && screenW >= screenH);
const int textY = y + (highlightHeight - FONT_HEIGHT_SMALL) / 2;
int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1;
// === Battery Icons ===
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
batteryX += 1;
batteryY += 2;
if (isHighResolution) {
display->drawXbm(batteryX, batteryY, 19, 12, imgUSB_HighResolution);
batteryX += 20; // Icon + 1 pixel
} else {
display->drawXbm(batteryX, batteryY, 10, 8, imgUSB);
batteryX += 11; // Icon + 1 pixel
}
} else {
if (useHorizontalBattery) {
batteryX += 1;
batteryY += 2;
display->drawXbm(batteryX, batteryY, 9, 13, batteryBitmap_h_bottom);
display->drawXbm(batteryX + 9, batteryY, 9, 13, batteryBitmap_h_top);
if (isCharging && isBoltVisibleShared)
display->drawXbm(batteryX + 4, batteryY, 9, 13, lightning_bolt_h);
else {
display->drawLine(batteryX + 5, batteryY, batteryX + 10, batteryY);
display->drawLine(batteryX + 5, batteryY + 12, batteryX + 10, batteryY + 12);
int fillWidth = 14 * chargePercent / 100;
display->fillRect(batteryX + 1, batteryY + 1, fillWidth, 11);
}
batteryX += 18; // Icon + 2 pixels
} else {
#ifdef USE_EINK
batteryY += 2;
#endif
display->drawXbm(batteryX, batteryY, 7, 11, batteryBitmap_v);
if (isCharging && isBoltVisibleShared)
display->drawXbm(batteryX + 1, batteryY + 3, 5, 5, lightning_bolt_v);
else {
display->drawXbm(batteryX - 1, batteryY + 4, 8, 3, batteryBitmap_sidegaps_v);
int fillHeight = 8 * chargePercent / 100;
int fillY = batteryY - fillHeight;
display->fillRect(batteryX + 1, fillY + 10, 5, fillHeight);
}
batteryX += 9; // Icon + 2 pixels
}
}
if (chargePercent != 101) {
// === Battery % Display ===
char chargeStr[4];
snprintf(chargeStr, sizeof(chargeStr), "%d", chargePercent);
int chargeNumWidth = display->getStringWidth(chargeStr);
display->drawString(batteryX, textY, chargeStr);
display->drawString(batteryX + chargeNumWidth - 1, textY, "%");
if (isBold) {
display->drawString(batteryX + 1, textY, chargeStr);
display->drawString(batteryX + chargeNumWidth, textY, "%");
}
}
// === Time and Right-aligned Icons ===
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
char timeStr[10] = "--:--"; // Fallback display
int timeStrWidth = display->getStringWidth("12:34"); // Default alignment
int timeX = screenW - xOffset - timeStrWidth + 4;
if (rtc_sec > 0 && !battery_only) {
// === Build Time String ===
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
int hour = hms / SEC_PER_HOUR;
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
if (config.display.use_12h_clock) {
bool isPM = hour >= 12;
hour %= 12;
if (hour == 0)
hour = 12;
snprintf(timeStr, sizeof(timeStr), "%d:%02d%s", hour, minute, isPM ? "p" : "a");
}
timeStrWidth = display->getStringWidth(timeStr);
timeX = screenW - xOffset - timeStrWidth + 3;
// === Show Mail or Mute Icon to the Left of Time ===
int iconRightEdge = timeX - 1;
bool showMail = false;
#ifndef USE_EINK
if (hasUnreadMessage) {
if (now - lastMailBlink > 500) {
isMailIconVisible = !isMailIconVisible;
lastMailBlink = now;
}
showMail = isMailIconVisible;
}
#else
if (hasUnreadMessage) {
showMail = true;
}
#endif
if (showMail) {
if (useHorizontalBattery) {
int iconW = 16, iconH = 12;
int iconX = iconRightEdge - iconW;
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
if (isInverted) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
display->setColor(BLACK);
} else {
display->setColor(BLACK);
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
display->setColor(WHITE);
}
display->drawRect(iconX, iconY, iconW + 1, iconH);
display->drawLine(iconX, iconY, iconX + iconW / 2, iconY + iconH - 4);
display->drawLine(iconX + iconW, iconY, iconX + iconW / 2, iconY + iconH - 4);
} else {
int iconX = iconRightEdge - (mail_width - 2);
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
if (isInverted) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
display->setColor(BLACK);
} else {
display->setColor(BLACK);
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
display->setColor(WHITE);
}
display->drawXbm(iconX, iconY, mail_width, mail_height, mail);
}
} else if (isMuted) {
if (isHighResolution) {
int iconX = iconRightEdge - mute_symbol_big_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
if (isInverted) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2);
display->setColor(BLACK);
} else {
display->setColor(BLACK);
display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2);
display->setColor(WHITE);
}
display->drawXbm(iconX, iconY, mute_symbol_big_width, mute_symbol_big_height, mute_symbol_big);
} else {
int iconX = iconRightEdge - mute_symbol_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
if (isInverted) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mute_symbol_width + 2, mute_symbol_height + 2);
display->setColor(BLACK);
} else {
display->setColor(BLACK);
display->fillRect(iconX - 1, iconY - 1, mute_symbol_width + 2, mute_symbol_height + 2);
display->setColor(WHITE);
}
display->drawXbm(iconX, iconY, mute_symbol_width, mute_symbol_height, mute_symbol);
}
}
// === Draw Time ===
display->drawString(timeX, textY, timeStr);
if (isBold)
display->drawString(timeX - 1, textY, timeStr);
} else {
// === No Time Available: Mail/Mute Icon Moves to Far Right ===
int iconRightEdge = screenW - xOffset;
bool showMail = false;
#ifndef USE_EINK
if (hasUnreadMessage) {
if (now - lastMailBlink > 500) {
isMailIconVisible = !isMailIconVisible;
lastMailBlink = now;
}
showMail = isMailIconVisible;
}
#else
if (hasUnreadMessage) {
showMail = true;
}
#endif
if (showMail) {
if (useHorizontalBattery) {
int iconW = 16, iconH = 12;
int iconX = iconRightEdge - iconW;
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
display->drawRect(iconX, iconY, iconW + 1, iconH);
display->drawLine(iconX, iconY, iconX + iconW / 2, iconY + iconH - 4);
display->drawLine(iconX + iconW, iconY, iconX + iconW / 2, iconY + iconH - 4);
} else {
int iconX = iconRightEdge - mail_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
display->drawXbm(iconX, iconY, mail_width, mail_height, mail);
}
} else if (isMuted) {
if (isHighResolution) {
int iconX = iconRightEdge - mute_symbol_big_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
display->drawXbm(iconX, iconY, mute_symbol_big_width, mute_symbol_big_height, mute_symbol_big);
} else {
int iconX = iconRightEdge - mute_symbol_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
display->drawXbm(iconX, iconY, mute_symbol_width, mute_symbol_height, mute_symbol);
}
}
}
display->setColor(WHITE); // Reset for other UI
}
const int *getTextPositions(OLEDDisplay *display)
{
static int textPositions[7]; // Static array that persists beyond function scope
if (isHighResolution) {
textPositions[0] = textZeroLine;
textPositions[1] = textFirstLine_medium;
textPositions[2] = textSecondLine_medium;
textPositions[3] = textThirdLine_medium;
textPositions[4] = textFourthLine_medium;
textPositions[5] = textFifthLine_medium;
textPositions[6] = textSixthLine_medium;
} else {
textPositions[0] = textZeroLine;
textPositions[1] = textFirstLine;
textPositions[2] = textSecondLine;
textPositions[3] = textThirdLine;
textPositions[4] = textFourthLine;
textPositions[5] = textFifthLine;
textPositions[6] = textSixthLine;
}
return textPositions;
}
bool isAllowedPunctuation(char c)
{
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
return allowed.find(c) != std::string::npos;
}
std::string sanitizeString(const std::string &input)
{
std::string output;
bool inReplacement = false;
for (char c : input) {
if (std::isalnum(static_cast<unsigned char>(c)) || isAllowedPunctuation(c)) {
output += c;
inReplacement = false;
} else {
if (!inReplacement) {
output += 0xbf; // ISO-8859-1 for inverted question mark
inReplacement = true;
}
}
}
return output;
}
} // namespace graphics

View File

@@ -0,0 +1,60 @@
#pragma once
#include <OLEDDisplay.h>
#include <string>
namespace graphics
{
// =======================
// Shared UI Helpers
// =======================
#define textZeroLine 0
// Consistent Line Spacing - this is standard for all display and the fall-back spacing
#define textFirstLine (FONT_HEIGHT_SMALL - 1)
#define textSecondLine (textFirstLine + (FONT_HEIGHT_SMALL - 5))
#define textThirdLine (textSecondLine + (FONT_HEIGHT_SMALL - 5))
#define textFourthLine (textThirdLine + (FONT_HEIGHT_SMALL - 5))
#define textFifthLine (textFourthLine + (FONT_HEIGHT_SMALL - 5))
#define textSixthLine (textFifthLine + (FONT_HEIGHT_SMALL - 5))
// Consistent Line Spacing for devices like T114 and TEcho/ThinkNode M1 of devices
#define textFirstLine_medium (FONT_HEIGHT_SMALL + 1)
#define textSecondLine_medium (textFirstLine_medium + FONT_HEIGHT_SMALL)
#define textThirdLine_medium (textSecondLine_medium + FONT_HEIGHT_SMALL)
#define textFourthLine_medium (textThirdLine_medium + FONT_HEIGHT_SMALL)
#define textFifthLine_medium (textFourthLine_medium + FONT_HEIGHT_SMALL)
#define textSixthLine_medium (textFifthLine_medium + FONT_HEIGHT_SMALL)
// Consistent Line Spacing for devices like VisionMaster T190
#define textFirstLine_large (FONT_HEIGHT_SMALL + 1)
#define textSecondLine_large (textFirstLine_large + (FONT_HEIGHT_SMALL + 5))
#define textThirdLine_large (textSecondLine_large + (FONT_HEIGHT_SMALL + 5))
#define textFourthLine_large (textThirdLine_large + (FONT_HEIGHT_SMALL + 5))
#define textFifthLine_large (textFourthLine_large + (FONT_HEIGHT_SMALL + 5))
#define textSixthLine_large (textFifthLine_large + (FONT_HEIGHT_SMALL + 5))
// Quick screen access
#define SCREEN_WIDTH display->getWidth()
#define SCREEN_HEIGHT display->getHeight()
// Shared state (declare inside namespace)
extern bool hasUnreadMessage;
extern bool isMuted;
extern bool isHighResolution;
void determineResolution(int16_t screenheight, int16_t screenwidth);
// Rounded highlight (used for inverted headers)
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
// Shared battery/time/mail header
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool battery_only = false);
const int *getTextPositions(OLEDDisplay *display);
bool isAllowedPunctuation(char c);
std::string sanitizeString(const std::string &input);
} // namespace graphics

View File

@@ -1,5 +1,6 @@
#include "configuration.h"
#include "main.h"
#if ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
#endif
@@ -14,8 +15,10 @@
extern SX1509 gpioExtender;
#endif
#ifndef TFT_MESH
#define TFT_MESH COLOR565(0x67, 0xEA, 0x94)
#ifdef TFT_MESH_OVERRIDE
uint16_t TFT_MESH = TFT_MESH_OVERRIDE;
#else
uint16_t TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
#endif
#if defined(ST7735S)
@@ -467,18 +470,27 @@ class LGFX : public lgfx::LGFX_Device
// The following setting values are general initial values for each panel, so please comment out any
// unknown items and try them.
cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
cfg.panel_width = TFT_WIDTH; // actual displayable width
cfg.panel_height = TFT_HEIGHT; // actual displayable height
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
#if defined(T_WATCH_S3)
cfg.panel_width = 240;
cfg.panel_height = 240;
cfg.memory_width = 240;
cfg.memory_height = 320;
cfg.offset_x = 0;
cfg.offset_y = 0; // No vertical shift needed — panel is top-aligned
cfg.offset_rotation = 2; // Rotate 180° to correct upside-down layout
#else
cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
cfg.panel_width = TFT_WIDTH; // actual displayable width
cfg.panel_height = TFT_HEIGHT; // actual displayable height
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
#endif
#ifdef TFT_DUMMY_READ_PIXELS
cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout
#else
cfg.dummy_read_pixel = 9; // Number of bits for dummy read before pixel readout
cfg.dummy_read_pixel = 9; // Number of bits for dummy read before pixel readout
#endif
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
cfg.readable = true; // Set to true if data can be read
@@ -653,7 +665,7 @@ static LGFX *tft = nullptr;
#include <TFT_eSPI.h> // Graphics and font library for ILI9342 driver chip
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
#elif ARCH_PORTDUINO && HAS_SCREEN != 0 && !HAS_TFT
#elif ARCH_PORTDUINO
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
class LGFX : public lgfx::LGFX_Device
@@ -697,11 +709,16 @@ class LGFX : public lgfx::LGFX_Device
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
LOG_DEBUG("Height: %d, Width: %d ", settingsMap[displayHeight], settingsMap[displayWidth]);
LOG_DEBUG("Width: %d, Height: %d", settingsMap[displayWidth], settingsMap[displayHeight]);
cfg.pin_cs = settingsMap[displayCS]; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = settingsMap[displayReset];
cfg.panel_width = settingsMap[displayWidth]; // actual displayable width
cfg.panel_height = settingsMap[displayHeight]; // actual displayable height
if (settingsMap[displayRotate]) {
cfg.panel_width = settingsMap[displayHeight]; // actual displayable width
cfg.panel_height = settingsMap[displayWidth]; // actual displayable height
} else {
cfg.panel_width = settingsMap[displayWidth]; // actual displayable width
cfg.panel_height = settingsMap[displayHeight]; // actual displayable height
}
cfg.offset_x = settingsMap[displayOffsetX]; // Panel offset amount in X direction
cfg.offset_y = settingsMap[displayOffsetY]; // Panel offset amount in Y direction
cfg.offset_rotation = settingsMap[displayOffsetRotate]; // Rotation direction value offset 0~7 (4~7 is mirrored)
@@ -978,9 +995,9 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
#if ARCH_PORTDUINO
if (settingsMap[displayRotate]) {
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]);
} else {
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayWidth], settingsMap[configNames::displayHeight]);
} else {
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]);
}
#elif defined(SCREEN_ROTATE)
@@ -1169,6 +1186,8 @@ bool TFTDisplay::connect()
tft->setRotation(1); // T-Deck has the TFT in landscape
#elif defined(T_WATCH_S3) || defined(SENSECAP_INDICATOR)
tft->setRotation(2); // T-Watch S3 left-handed orientation
#elif ARCH_PORTDUINO
tft->setRotation(0); // use config.yaml to set rotation
#else
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
#endif

View File

@@ -0,0 +1,103 @@
#include "TimeFormatters.h"
#include "configuration.h"
#include "gps/RTC.h"
#include "mesh/NodeDB.h"
#include <cstring>
bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int32_t *daysAgo)
{
// Cache the result - avoid frequent recalculation
static uint8_t hoursCached = 0, minutesCached = 0;
static uint32_t daysAgoCached = 0;
static uint32_t secondsAgoCached = 0;
static bool validCached = false;
// Abort: if timezone not set
if (strlen(config.device.tzdef) == 0) {
validCached = false;
return validCached;
}
// Abort: if invalid pointers passed
if (hours == nullptr || minutes == nullptr || daysAgo == nullptr) {
validCached = false;
return validCached;
}
// Abort: if time seems invalid.. (> 6 months ago, probably seen before RTC set)
if (secondsAgo > SEC_PER_DAY * 30UL * 6) {
validCached = false;
return validCached;
}
// If repeated request, don't bother recalculating
if (secondsAgo - secondsAgoCached < 60 && secondsAgoCached != 0) {
if (validCached) {
*hours = hoursCached;
*minutes = minutesCached;
*daysAgo = daysAgoCached;
}
return validCached;
}
// Get local time
uint32_t secondsRTC = getValidTime(RTCQuality::RTCQualityDevice, true); // Get local time
// Abort: if RTC not set
if (!secondsRTC) {
validCached = false;
return validCached;
}
// Get absolute time when last seen
uint32_t secondsSeenAt = secondsRTC - secondsAgo;
// Calculate daysAgo
*daysAgo = (secondsRTC / SEC_PER_DAY) - (secondsSeenAt / SEC_PER_DAY); // How many "midnights" have passed
// Get seconds since midnight
uint32_t hms = (secondsRTC - secondsAgo) % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into hours and minutes
*hours = hms / SEC_PER_HOUR;
*minutes = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
// Cache the result
daysAgoCached = *daysAgo;
hoursCached = *hours;
minutesCached = *minutes;
secondsAgoCached = secondsAgo;
validCached = true;
return validCached;
}
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
{
// Use an absolute timestamp in some cases.
// Particularly useful with E-Ink displays. Static UI, fewer refreshes.
uint8_t timestampHours, timestampMinutes;
int32_t daysAgo;
bool useTimestamp = deltaToTimestamp(agoSecs, &timestampHours, &timestampMinutes, &daysAgo);
if (agoSecs < 120) // last 2 mins?
snprintf(timeStr, maxLength, "%u seconds ago", agoSecs);
// -- if suitable for timestamp --
else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes
snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / SECONDS_IN_MINUTE);
else if (useTimestamp && daysAgo == 0) // Today
snprintf(timeStr, maxLength, "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes);
else if (useTimestamp && daysAgo == 1) // Yesterday
snprintf(timeStr, maxLength, "Seen yesterday");
else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method)
snprintf(timeStr, maxLength, "%li days ago", (long)daysAgo);
// -- if using time delta instead --
else if (agoSecs < 120 * 60) // last 2 hrs
snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / 60);
// Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data.
else if ((agoSecs / 60 / 60) < (730 * 6))
snprintf(timeStr, maxLength, "%u hours ago", agoSecs / 60 / 60);
else
snprintf(timeStr, maxLength, "unknown age");
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include "configuration.h"
#include "gps/RTC.h"
#include <airtime.h>
#include <cstdint>
/**
* Convert a delta in seconds ago to timestamp information (hours, minutes, days ago).
*
* @param secondsAgo Number of seconds ago to convert
* @param hours Pointer to store the hours (0-23)
* @param minutes Pointer to store the minutes (0-59)
* @param daysAgo Pointer to store the number of days ago
* @return true if conversion was successful, false if invalid input or time not available
*/
bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int32_t *daysAgo);
/**
* Get a human-readable string representing the time ago in a format like "2 days, 3 hours, 15 minutes".
*
* @param agoSecs Number of seconds ago to convert
* @param timeStr Pointer to store the resulting string
* @param maxLength Maximum length of the resulting string buffer
*/
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);

View File

@@ -0,0 +1,516 @@
#include "configuration.h"
#if HAS_SCREEN
#include "ClockRenderer.h"
#include "NodeDB.h"
#include "UIRenderer.h"
#include "configuration.h"
#include "gps/GeoCoord.h"
#include "gps/RTC.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/emotes.h"
#include "graphics/images.h"
#include "main.h"
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
#include "nimble/NimbleBluetooth.h"
#endif
namespace graphics
{
namespace ClockRenderer
{
void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale)
{
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
uint16_t cellHeight = (segmentWidth * 2) + (segmentHeight * 3) + 8;
uint16_t topAndBottomX = x + (4 * scale);
uint16_t quarterCellHeight = cellHeight / 4;
uint16_t topY = y + quarterCellHeight;
uint16_t bottomY = y + (quarterCellHeight * 3);
display->fillRect(topAndBottomX, topY, segmentHeight, segmentHeight);
display->fillRect(topAndBottomX, bottomY, segmentHeight, segmentHeight);
}
void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale)
{
// the numbers 0-9, each expressed as an array of seven boolean (0|1) values encoding the on/off state of
// segment {innerIndex + 1}
// e.g., to display the numeral '0', segments 1-6 are on, and segment 7 is off.
uint8_t numbers[10][7] = {
{1, 1, 1, 1, 1, 1, 0}, // 0 Display segment key
{0, 1, 1, 0, 0, 0, 0}, // 1 1
{1, 1, 0, 1, 1, 0, 1}, // 2 ___
{1, 1, 1, 1, 0, 0, 1}, // 3 6 | | 2
{0, 1, 1, 0, 0, 1, 1}, // 4 |_7̲_|
{1, 0, 1, 1, 0, 1, 1}, // 5 5 | | 3
{1, 0, 1, 1, 1, 1, 1}, // 6 |___|
{1, 1, 1, 0, 0, 1, 0}, // 7
{1, 1, 1, 1, 1, 1, 1}, // 8 4
{1, 1, 1, 1, 0, 1, 1}, // 9
};
// the width and height of each segment's central rectangle:
// _____________________
// ⋰| (only this part, |⋱
// ⋰ | not including | ⋱
// ⋱ | the triangles | ⋰
// ⋱| on the ends) |⋰
// ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
// segment x and y coordinates
uint16_t segmentOneX = x + segmentHeight + 2;
uint16_t segmentOneY = y;
uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
uint16_t segmentThreeX = segmentTwoX;
uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2 + segmentHeight + 2;
uint16_t segmentFourX = segmentOneX;
uint16_t segmentFourY = segmentThreeY + segmentWidth + 2;
uint16_t segmentFiveX = x;
uint16_t segmentFiveY = segmentThreeY;
uint16_t segmentSixX = x;
uint16_t segmentSixY = segmentTwoY;
uint16_t segmentSevenX = segmentOneX;
uint16_t segmentSevenY = segmentTwoY + segmentWidth + 2;
if (numbers[number][0]) {
graphics::ClockRenderer::drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
}
if (numbers[number][1]) {
graphics::ClockRenderer::drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
}
if (numbers[number][2]) {
graphics::ClockRenderer::drawVerticalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
}
if (numbers[number][3]) {
graphics::ClockRenderer::drawHorizontalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
}
if (numbers[number][4]) {
graphics::ClockRenderer::drawVerticalSegment(display, segmentFiveX, segmentFiveY, segmentWidth, segmentHeight);
}
if (numbers[number][5]) {
graphics::ClockRenderer::drawVerticalSegment(display, segmentSixX, segmentSixY, segmentWidth, segmentHeight);
}
if (numbers[number][6]) {
graphics::ClockRenderer::drawHorizontalSegment(display, segmentSevenX, segmentSevenY, segmentWidth, segmentHeight);
}
}
void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height)
{
int halfHeight = height / 2;
// draw central rectangle
display->fillRect(x, y, width, height);
// draw end triangles
display->fillTriangle(x, y, x, y + height - 1, x - halfHeight, y + halfHeight);
display->fillTriangle(x + width, y, x + width + halfHeight, y + halfHeight, x + width, y + height - 1);
}
void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height)
{
int halfHeight = height / 2;
// draw central rectangle
display->fillRect(x, y, height, width);
// draw end triangles
display->fillTriangle(x + halfHeight, y - halfHeight, x + height - 1, y, x, y);
display->fillTriangle(x, y + width, x + height - 1, y + width, x + halfHeight, y + width + halfHeight);
}
/*
void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode, float scale)
{
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
if (digitalMode) {
uint16_t radius = (segmentWidth + (segmentHeight * 2) + 4) / 2;
uint16_t centerX = (x + segmentHeight + 2) + (radius / 2);
uint16_t centerY = (y + segmentHeight + 2) + (radius / 2);
display->drawCircle(centerX, centerY, radius);
display->drawCircle(centerX, centerY, radius + 1);
display->drawLine(centerX, centerY, centerX, centerY - radius + 3);
display->drawLine(centerX, centerY, centerX + radius - 3, centerY);
} else {
uint16_t segmentOneX = x + segmentHeight + 2;
uint16_t segmentOneY = y;
uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
uint16_t segmentThreeX = segmentOneX;
uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2;
uint16_t segmentFourX = x;
uint16_t segmentFourY = y + segmentHeight + 2;
drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
drawHorizontalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
drawVerticalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
}
}
*/
// Draw a digital clock
void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
int line = 1;
// === Set Title, Blank for Clock
const char *titleStr = "";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr, true);
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
graphics::ClockRenderer::drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
}
#endif
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
char timeString[16];
int hour = 0;
int minute = 0;
int second = 0;
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
hour = hms / SEC_PER_HOUR;
minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
}
bool isPM = hour >= 12;
// hour = hour > 12 ? hour - 12 : hour;
if (config.display.use_12h_clock) {
hour %= 12;
if (hour == 0)
hour = 12;
bool isPM = hour >= 12;
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
} else {
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
}
// Format seconds string
char secondString[8];
snprintf(secondString, sizeof(secondString), "%02d", second);
#ifdef T_WATCH_S3
float scale = 1.5;
#else
float scale = 0.75;
if (isHighResolution) {
scale = 1.5;
}
#endif
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
// calculate hours:minutes string width
uint16_t timeStringWidth = strlen(timeString) * 5;
for (uint8_t i = 0; i < strlen(timeString); i++) {
char character = timeString[i];
if (character == ':') {
timeStringWidth += segmentHeight;
} else {
timeStringWidth += segmentWidth + (segmentHeight * 2) + 4;
}
}
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
uint16_t startingHourMinuteTextX = hourMinuteTextX;
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2);
// iterate over characters in hours:minutes string and draw segmented characters
for (uint8_t i = 0; i < strlen(timeString); i++) {
char character = timeString[i];
if (character == ':') {
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
hourMinuteTextX += segmentHeight + 6;
} else {
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
hourMinuteTextX += segmentWidth + (segmentHeight * 2) + 4;
}
hourMinuteTextX += 5;
}
// draw seconds string
display->setFont(FONT_SMALL);
int xOffset = (isHighResolution) ? 0 : -1;
if (hour >= 10) {
xOffset += (isHighResolution) ? 32 : 18;
}
int yOffset = (isHighResolution) ? 3 : 1;
#ifdef SENSECAP_INDICATOR
yOffset -= 3;
#endif
if (config.display.use_12h_clock) {
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
isPM ? "pm" : "am");
}
#ifndef USE_EINK
xOffset = (isHighResolution) ? 18 : 10;
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
secondString);
#endif
}
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
{
display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
}
// Draw an analog clock
void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
// === Set Title, Blank for Clock
const char *titleStr = "";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr, true);
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
}
#endif
// clock face center coordinates
int16_t centerX = display->getWidth() / 2;
int16_t centerY = display->getHeight() / 2;
// clock face radius
int16_t radius = 0;
if (display->getHeight() < display->getWidth()) {
radius = (display->getHeight() / 2) * 0.9;
} else {
radius = (display->getWidth() / 2) * 0.9;
}
#ifdef T_WATCH_S3
radius = (display->getWidth() / 2) * 0.8;
#endif
// noon (0 deg) coordinates (outermost circle)
int16_t noonX = centerX;
int16_t noonY = centerY - radius;
// second hand radius and y coordinate (outermost circle)
int16_t secondHandNoonY = noonY + 1;
// tick mark outer y coordinate; (first nested circle)
int16_t tickMarkOuterNoonY = secondHandNoonY;
// seconds tick mark inner y coordinate; (second nested circle)
double secondsTickMarkInnerNoonY = (double)noonY + 4;
if (isHighResolution) {
secondsTickMarkInnerNoonY = (double)noonY + 8;
}
// hours tick mark inner y coordinate; (third nested circle)
double hoursTickMarkInnerNoonY = (double)noonY + 6;
if (isHighResolution) {
hoursTickMarkInnerNoonY = (double)noonY + 16;
}
// minute hand y coordinate
int16_t minuteHandNoonY = secondsTickMarkInnerNoonY + 4;
// hour string y coordinate
int16_t hourStringNoonY = minuteHandNoonY + 18;
// hour hand radius and y coordinate
int16_t hourHandRadius = radius * 0.35;
if (isHighResolution) {
int16_t hourHandRadius = radius * 0.55;
}
int16_t hourHandNoonY = centerY - hourHandRadius;
display->setColor(OLEDDISPLAY_COLOR::WHITE);
display->drawCircle(centerX, centerY, radius);
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
bool isPM = hour >= 12;
if (config.display.use_12h_clock) {
bool isPM = hour >= 12;
display->setFont(FONT_SMALL);
int yOffset = isHighResolution ? 1 : 0;
#ifdef USE_EINK
yOffset += 3;
#endif
display->drawString(centerX - (display->getStringWidth(isPM ? "pm" : "am") / 2), centerY + yOffset,
isPM ? "pm" : "am");
}
hour %= 12;
if (hour == 0)
hour = 12;
int16_t degreesPerHour = 30;
int16_t degreesPerMinuteOrSecond = 6;
double hourBaseAngle = hour * degreesPerHour;
double hourAngleOffset = ((double)minute / 60) * degreesPerHour;
double hourAngle = radians(hourBaseAngle + hourAngleOffset);
double minuteBaseAngle = minute * degreesPerMinuteOrSecond;
double minuteAngleOffset = ((double)second / 60) * degreesPerMinuteOrSecond;
double minuteAngle = radians(minuteBaseAngle + minuteAngleOffset);
double secondAngle = radians(second * degreesPerMinuteOrSecond);
double hourX = sin(-hourAngle) * (hourHandNoonY - centerY) + noonX;
double hourY = cos(-hourAngle) * (hourHandNoonY - centerY) + centerY;
double minuteX = sin(-minuteAngle) * (minuteHandNoonY - centerY) + noonX;
double minuteY = cos(-minuteAngle) * (minuteHandNoonY - centerY) + centerY;
double secondX = sin(-secondAngle) * (secondHandNoonY - centerY) + noonX;
double secondY = cos(-secondAngle) * (secondHandNoonY - centerY) + centerY;
display->setFont(FONT_MEDIUM);
// draw minute and hour tick marks and hour numbers
for (uint16_t angle = 0; angle < 360; angle += 6) {
double angleInRadians = radians(angle);
double sineAngleInRadians = sin(-angleInRadians);
double cosineAngleInRadians = cos(-angleInRadians);
double endX = sineAngleInRadians * (tickMarkOuterNoonY - centerY) + noonX;
double endY = cosineAngleInRadians * (tickMarkOuterNoonY - centerY) + centerY;
if (angle % degreesPerHour == 0) {
double startX = sineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + noonX;
double startY = cosineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + centerY;
// draw hour tick mark
display->drawLine(startX, startY, endX, endY);
static char buffer[2];
uint8_t hourInt = (angle / 30);
if (hourInt == 0) {
hourInt = 12;
}
// hour number x offset needs to be adjusted for some cases
int8_t hourStringXOffset;
int8_t hourStringYOffset = 13;
switch (hourInt) {
case 3:
hourStringXOffset = 5;
break;
case 9:
hourStringXOffset = 7;
break;
case 10:
case 11:
hourStringXOffset = 8;
break;
case 12:
hourStringXOffset = 13;
break;
default:
hourStringXOffset = 6;
break;
}
double hourStringX = (sineAngleInRadians * (hourStringNoonY - centerY) + noonX) - hourStringXOffset;
double hourStringY = (cosineAngleInRadians * (hourStringNoonY - centerY) + centerY) - hourStringYOffset;
#ifdef T_WATCH_S3
// draw hour number
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
#else
#ifdef USE_EINK
if (isHighResolution) {
// draw hour number
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
}
#else
if (isHighResolution && (hourInt == 3 || hourInt == 6 || hourInt == 9 || hourInt == 12)) {
// draw hour number
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
}
#endif
#endif
}
if (angle % degreesPerMinuteOrSecond == 0) {
double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX;
double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY;
if (isHighResolution) {
// draw minute tick mark
display->drawLine(startX, startY, endX, endY);
}
}
}
// draw hour hand
display->drawLine(centerX, centerY, hourX, hourY);
// draw minute hand
display->drawLine(centerX, centerY, minuteX, minuteY);
#ifndef USE_EINK
// draw second hand
display->drawLine(centerX, centerY, secondX, secondY);
#endif
}
}
} // namespace ClockRenderer
} // namespace graphics
#endif

View File

@@ -0,0 +1,31 @@
#pragma once
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>
namespace graphics
{
/// Forward declarations
class Screen;
namespace ClockRenderer
{
// Clock frame functions
void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// Segmented display functions
void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale = 1);
void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale = 1);
void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height);
void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height);
// UI elements for clock displays
// void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
} // namespace ClockRenderer
} // namespace graphics

View File

@@ -0,0 +1,137 @@
#include "CompassRenderer.h"
#include "NodeDB.h"
#include "UIRenderer.h"
#include "configuration.h"
#include "gps/GeoCoord.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include <cmath>
namespace graphics
{
namespace CompassRenderer
{
// Point helper class for compass calculations
struct Point {
float x, y;
Point(float x, float y) : x(x), y(y) {}
void rotate(float angle)
{
float cos_a = cos(angle);
float sin_a = sin(angle);
float new_x = x * cos_a - y * sin_a;
float new_y = x * sin_a + y * cos_a;
x = new_x;
y = new_y;
}
void scale(float factor)
{
x *= factor;
y *= factor;
}
void translate(float dx, float dy)
{
x += dx;
y += dy;
}
};
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading, int16_t radius)
{
// Show the compass heading (not implemented in original)
// This could draw a "N" indicator or north arrow
// For now, we'll draw a simple north indicator
// const float radius = 17.0f;
if (isHighResolution) {
radius += 4;
}
Point north(0, -radius);
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
north.rotate(-myHeading);
north.translate(compassX, compassY);
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setColor(BLACK);
if (isHighResolution) {
display->fillRect(north.x - 8, north.y - 1, display->getStringWidth("N") + 3, FONT_HEIGHT_SMALL - 6);
} else {
display->fillRect(north.x - 4, north.y - 1, display->getStringWidth("N") + 2, FONT_HEIGHT_SMALL - 6);
}
display->setColor(WHITE);
display->drawString(north.x, north.y - 3, "N");
}
void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian)
{
Point tip(0.0f, -0.5f), tail(0.0f, 0.35f); // pointing up initially
float arrowOffsetX = 0.14f, arrowOffsetY = 0.9f;
Point leftArrow(tip.x - arrowOffsetX, tip.y + arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y + arrowOffsetY);
Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow};
for (int i = 0; i < 4; i++) {
arrowPoints[i]->rotate(headingRadian);
arrowPoints[i]->scale(compassDiam * 0.6);
arrowPoints[i]->translate(compassX, compassY);
}
#ifdef USE_EINK
display->drawTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y);
#else
display->fillTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y);
#endif
display->drawTriangle(tip.x, tip.y, leftArrow.x, leftArrow.y, tail.x, tail.y);
}
void drawArrowToNode(OLEDDisplay *display, int16_t x, int16_t y, int16_t size, float bearing)
{
float radians = bearing * DEG_TO_RAD;
Point tip(0, -size / 2);
Point left(-size / 6, size / 4);
Point right(size / 6, size / 4);
Point tail(0, size / 4.5);
tip.rotate(radians);
left.rotate(radians);
right.rotate(radians);
tail.rotate(radians);
tip.translate(x, y);
left.translate(x, y);
right.translate(x, y);
tail.translate(x, y);
display->fillTriangle(tip.x, tip.y, left.x, left.y, tail.x, tail.y);
display->fillTriangle(tip.x, tip.y, right.x, right.y, tail.x, tail.y);
}
float estimatedHeading(double lat, double lon)
{
// Simple magnetic declination estimation
// This is a very basic implementation - the original might be more sophisticated
return 0.0f; // Return 0 for now, indicating no heading available
}
uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
{
// Calculate appropriate compass diameter based on display size
uint16_t minDimension = (displayWidth < displayHeight) ? displayWidth : displayHeight;
uint16_t maxDiam = minDimension / 3; // Use 1/3 of the smaller dimension
// Ensure minimum and maximum bounds
if (maxDiam < 16)
maxDiam = 16;
if (maxDiam > 64)
maxDiam = 64;
return maxDiam;
}
} // namespace CompassRenderer
} // namespace graphics

View File

@@ -0,0 +1,33 @@
#pragma once
#include "graphics/Screen.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>
namespace graphics
{
/// Forward declarations
class Screen;
/**
* @brief Compass and navigation drawing functions
*
* Contains all functions related to drawing compass elements, headings,
* navigation arrows, and location-based UI components.
*/
namespace CompassRenderer
{
// Compass drawing functions
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading, int16_t radius);
void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian);
void drawArrowToNode(OLEDDisplay *display, int16_t x, int16_t y, int16_t size, float bearing);
// Navigation and location functions
float estimatedHeading(double lat, double lon);
uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight);
} // namespace CompassRenderer
} // namespace graphics

View File

@@ -0,0 +1,622 @@
#include "configuration.h"
#if HAS_SCREEN
#include "../Screen.h"
#include "DebugRenderer.h"
#include "FSCommon.h"
#include "NodeDB.h"
#include "Throttle.h"
#include "UIRenderer.h"
#include "airtime.h"
#include "gps/RTC.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/images.h"
#include "main.h"
#include "mesh/Channels.h"
#include "mesh/generated/meshtastic/deviceonly.pb.h"
#include "sleep.h"
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
#include "mesh/wifi/WiFiAPClient.h"
#include <WiFi.h>
#ifdef ARCH_ESP32
#include "mesh/wifi/WiFiAPClient.h"
#endif
#endif
#ifdef ARCH_ESP32
#include "modules/StoreForwardModule.h"
#endif
#include <DisplayFormatters.h>
#include <RadioLibInterface.h>
#include <target_specific.h>
using namespace meshtastic;
// External variables
extern graphics::Screen *screen;
extern PowerStatus *powerStatus;
extern NodeStatus *nodeStatus;
extern GPSStatus *gpsStatus;
extern Channels channels;
extern AirTime *airTime;
// External functions from Screen.cpp
extern bool heartbeat;
#ifdef ARCH_ESP32
extern StoreForwardModule *storeForwardModule;
#endif
namespace graphics
{
namespace DebugRenderer
{
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setFont(FONT_SMALL);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
}
char channelStr[20];
snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex()));
// Display nodes status
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
UIRenderer::drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
} else {
UIRenderer::drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus);
}
#if HAS_GPS
// Display GPS status
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
UIRenderer::drawGpsPowerStatus(display, x, y + 2, gpsStatus);
} else {
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
UIRenderer::drawGps(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
} else {
UIRenderer::drawGps(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus);
}
}
#endif
display->setColor(WHITE);
// Draw the channel name
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
// Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo
if (moduleConfig.store_forward.enabled) {
#ifdef ARCH_ESP32
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
8, imgQuestionL1);
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 11 + FONT_HEIGHT_SMALL, 12,
8, imgQuestionL2);
#else
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(screen->ourId), y + 2 + FONT_HEIGHT_SMALL, 8,
8, imgQuestion);
#endif
} else {
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
8, imgSFL1);
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 11 + FONT_HEIGHT_SMALL, 16,
8, imgSFL2);
#else
display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(screen->ourId), y + 2 + FONT_HEIGHT_SMALL, 11,
8, imgSF);
#endif
}
#endif
} else {
// TODO: Raspberry Pi supports more than just the one screen size
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1);
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL2);
#else
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(screen->ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8,
imgInfo);
#endif
}
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(screen->ourId), y + FONT_HEIGHT_SMALL, screen->ourId);
// Draw any log messages
display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2));
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
display->setPixel(0, 0);
heartbeat = !heartbeat;
#endif
}
// ****************************
// * WiFi Screen *
// ****************************
void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
// === Set Title
const char *titleStr = "WiFi";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
const char *wifiName = config.network.wifi_ssid;
if (WiFi.status() != WL_CONNECTED) {
display->drawString(x, getTextPositions(display)[line++], "WiFi: Not Connected");
} else {
display->drawString(x, getTextPositions(display)[line++], "WiFi: Connected");
char rssiStr[32];
snprintf(rssiStr, sizeof(rssiStr), "RSSI: %d", WiFi.RSSI());
display->drawString(x, getTextPositions(display)[line++], rssiStr);
}
/*
- WL_CONNECTED: assigned when connected to a WiFi network;
- WL_NO_SSID_AVAIL: assigned when no SSID are available;
- WL_CONNECT_FAILED: assigned when the connection fails for all the attempts;
- WL_CONNECTION_LOST: assigned when the connection is lost;
- WL_DISCONNECTED: assigned when disconnected from a network;
- WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of
attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED);
- WL_SCAN_COMPLETED: assigned when the scan networks is completed;
- WL_NO_SHIELD: assigned when no WiFi shield is present;
*/
if (WiFi.status() == WL_CONNECTED) {
char ipStr[64];
snprintf(ipStr, sizeof(ipStr), "IP: %s", WiFi.localIP().toString().c_str());
display->drawString(x, getTextPositions(display)[line++], ipStr);
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
display->drawString(x, getTextPositions(display)[line++], "SSID Not Found");
} else if (WiFi.status() == WL_CONNECTION_LOST) {
display->drawString(x, getTextPositions(display)[line++], "Connection Lost");
} else if (WiFi.status() == WL_IDLE_STATUS) {
display->drawString(x, getTextPositions(display)[line++], "Idle ... Reconnecting");
} else if (WiFi.status() == WL_CONNECT_FAILED) {
display->drawString(x, getTextPositions(display)[line++], "Connection Failed");
}
#ifdef ARCH_ESP32
else {
// Codes:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
display->drawString(x, getTextPositions(display)[line++],
WiFi.disconnectReasonName(static_cast<wifi_err_reason_t>(getWifiDisconnectReason())));
}
#else
else {
char statusStr[32];
snprintf(statusStr, sizeof(statusStr), "Unknown status: %d", WiFi.status());
display->drawString(x, getTextPositions(display)[line++], statusStr);
}
#endif
char ssidStr[64];
snprintf(ssidStr, sizeof(ssidStr), "SSID: %s", wifiName);
display->drawString(x, getTextPositions(display)[line++], ssidStr);
display->drawString(x, getTextPositions(display)[line++], "URL: http://meshtastic.local");
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
display->setPixel(0, 0);
heartbeat = !heartbeat;
#endif
#endif
}
void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setFont(FONT_SMALL);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
}
char batStr[20];
if (powerStatus->getHasBattery()) {
int batV = powerStatus->getBatteryVoltageMv() / 1000;
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(),
powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' ');
// Line 1
display->drawString(x, y, batStr);
if (config.display.heading_bold)
display->drawString(x + 1, y, batStr);
} else {
// Line 1
display->drawString(x, y, "USB");
if (config.display.heading_bold)
display->drawString(x + 1, y, "USB");
}
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
// if (config.display.heading_bold)
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
uint32_t currentMillis = millis();
uint32_t seconds = currentMillis / 1000;
uint32_t minutes = seconds / 60;
uint32_t hours = minutes / 60;
uint32_t days = hours / 24;
// currentMillis %= 1000;
// seconds %= 60;
// minutes %= 60;
// hours %= 24;
// Show uptime as days, hours, minutes OR seconds
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
// Line 1 (Still)
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
if (config.display.heading_bold)
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
display->setColor(WHITE);
// Setup string to assemble analogClock string
std::string analogClock = "";
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
char timebuf[12];
if (config.display.use_12h_clock) {
std::string meridiem = "am";
if (hour >= 12) {
if (hour > 12)
hour -= 12;
meridiem = "pm";
}
if (hour == 00) {
hour = 12;
}
snprintf(timebuf, sizeof(timebuf), "%d:%02d:%02d%s", hour, min, sec, meridiem.c_str());
} else {
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", hour, min, sec);
}
analogClock += timebuf;
}
// Line 2
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, analogClock.c_str());
// Display Channel Utilization
char chUtil[13];
snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent());
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil);
#if HAS_GPS
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
// Line 3
if (config.display.gps_format !=
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
UIRenderer::drawGpsAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
// Line 4
UIRenderer::drawGpsCoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
} else {
UIRenderer::drawGpsPowerStatus(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
}
#endif
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
display->setPixel(0, 0);
heartbeat = !heartbeat;
#endif
}
// Trampoline functions for DebugInfo class access
void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
drawFrame(display, state, x, y);
}
void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
drawFrameSettings(display, state, x, y);
}
void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
drawFrameWiFi(display, state, x, y);
}
// ****************************
// * LoRa Focused Screen *
// ****************************
void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
// === Set Title
const char *titleStr = (isHighResolution) ? "LoRa Info" : "LoRa";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
// === First Row: Region / BLE Name ===
graphics::UIRenderer::drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, 0, true, "");
uint8_t dmac[6];
char shortnameble[35];
getMacAddr(dmac);
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
int textWidth = display->getStringWidth(shortnameble);
int nameX = (SCREEN_WIDTH - textWidth);
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
// === Second Row: Radio Preset ===
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
char regionradiopreset[25];
const char *region = myRegion ? myRegion->name : NULL;
if (region != nullptr) {
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
}
textWidth = display->getStringWidth(regionradiopreset);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], regionradiopreset);
// === Third Row: Frequency / ChanNum ===
char frequencyslot[35];
char freqStr[16];
float freq = RadioLibInterface::instance->getFreq();
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
if (config.lora.channel_num == 0) {
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %smhz", freqStr);
} else {
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %smhz (%d)", freqStr, config.lora.channel_num);
}
size_t len = strlen(frequencyslot);
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
frequencyslot[len - 4] = '\0'; // Remove the last three characters
}
textWidth = display->getStringWidth(frequencyslot);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
// === Fourth Row: Channel Utilization ===
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
int chUtil_x = (isHighResolution) ? display->getStringWidth(chUtil) + 10 : display->getStringWidth(chUtil) + 5;
int chUtil_y = getTextPositions(display)[line] + 3;
int chutil_bar_width = (isHighResolution) ? 100 : 50;
int chutil_bar_height = (isHighResolution) ? 12 : 7;
int extraoffset = (isHighResolution) ? 6 : 3;
int chutil_percent = airTime->channelUtilizationPercent();
int centerofscreen = SCREEN_WIDTH / 2;
int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
int starting_position = centerofscreen - total_line_content_width;
display->drawString(starting_position, getTextPositions(display)[line++], chUtil);
// Force 56% or higher to show a full 100% bar, text would still show related percent.
if (chutil_percent >= 61) {
chutil_percent = 100;
}
// Weighting for nonlinear segments
float milestone1 = 25;
float milestone2 = 40;
float weight1 = 0.45; // Weight for 025%
float weight2 = 0.35; // Weight for 2540%
float weight3 = 0.20; // Weight for 40100%
float totalWeight = weight1 + weight2 + weight3;
int seg1 = chutil_bar_width * (weight1 / totalWeight);
int seg2 = chutil_bar_width * (weight2 / totalWeight);
int seg3 = chutil_bar_width * (weight3 / totalWeight);
int fillRight = 0;
if (chutil_percent <= milestone1) {
fillRight = (seg1 * (chutil_percent / milestone1));
} else if (chutil_percent <= milestone2) {
fillRight = seg1 + (seg2 * ((chutil_percent - milestone1) / (milestone2 - milestone1)));
} else {
fillRight = seg1 + seg2 + (seg3 * ((chutil_percent - milestone2) / (100 - milestone2)));
}
// Draw outline
display->drawRect(starting_position + chUtil_x, chUtil_y, chutil_bar_width, chutil_bar_height);
// Fill progress
if (fillRight > 0) {
display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height);
}
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[4],
chUtilPercentage);
}
// ****************************
// * Memory Screen *
// ****************************
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->clear();
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
// === Set Title
const char *titleStr = "System";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
// === Layout ===
int line = 1;
const int barHeight = 6;
const int labelX = x;
int barsOffset = (isHighResolution) ? 24 : 0;
#ifdef USE_EINK
barsOffset -= 12;
#endif
const int barX = x + 40 + barsOffset;
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
if (total == 0)
return;
int percent = (used * 100) / total;
char combinedStr[24];
if (isHighResolution) {
snprintf(combinedStr, sizeof(combinedStr), "%s%3d%% %u/%uKB", (percent > 80) ? "! " : "", percent, used / 1024,
total / 1024);
} else {
snprintf(combinedStr, sizeof(combinedStr), "%s%3d%%", (percent > 80) ? "! " : "", percent);
}
int textWidth = display->getStringWidth(combinedStr);
int adjustedBarWidth = SCREEN_WIDTH - barX - textWidth - 6;
if (adjustedBarWidth < 10)
adjustedBarWidth = 10;
int fillWidth = (used * adjustedBarWidth) / total;
// Label
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(labelX, getTextPositions(display)[line], label);
// Bar
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
display->setColor(WHITE);
display->drawRect(barX, barY, adjustedBarWidth, barHeight);
display->fillRect(barX, barY, fillWidth, barHeight);
display->setColor(WHITE);
// Value string
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
};
// === Memory values ===
uint32_t heapUsed = memGet.getHeapSize() - memGet.getFreeHeap();
uint32_t heapTotal = memGet.getHeapSize();
uint32_t psramUsed = memGet.getPsramSize() - memGet.getFreePsram();
uint32_t psramTotal = memGet.getPsramSize();
uint32_t flashUsed = 0, flashTotal = 0;
#ifdef ESP32
flashUsed = FSCom.usedBytes();
flashTotal = FSCom.totalBytes();
#endif
uint32_t sdUsed = 0, sdTotal = 0;
bool hasSD = false;
/*
#ifdef HAS_SDCARD
hasSD = SD.cardType() != CARD_NONE;
if (hasSD) {
sdUsed = SD.usedBytes();
sdTotal = SD.totalBytes();
}
#endif
*/
// === Draw memory rows
drawUsageRow("Heap:", heapUsed, heapTotal, true);
#ifdef ESP32
if (psramUsed > 0) {
line += 1;
drawUsageRow("PSRAM:", psramUsed, psramTotal);
}
if (flashTotal > 0) {
line += 1;
drawUsageRow("Flash:", flashUsed, flashTotal);
}
#endif
if (hasSD && sdTotal > 0) {
line += 1;
drawUsageRow("SD:", sdUsed, sdTotal);
}
display->setTextAlignment(TEXT_ALIGN_LEFT);
// System Uptime
if (line < 2) {
line += 1;
}
line += 1;
char appversionstr[35];
snprintf(appversionstr, sizeof(appversionstr), "Ver.: %s", optstr(APP_VERSION));
int textWidth = display->getStringWidth(appversionstr);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
line += 1;
char uptimeStr[32] = "";
uint32_t uptime = millis() / 1000;
uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
if (days)
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %ud %uh", days, hours);
else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
textWidth = display->getStringWidth(uptimeStr);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
}
}
} // namespace DebugRenderer
} // namespace graphics
#endif

View File

@@ -0,0 +1,38 @@
#pragma once
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>
namespace graphics
{
/// Forward declarations
class Screen;
class DebugInfo;
/**
* @brief Debug and diagnostic drawing functions
*
* Contains all functions related to drawing debug information,
* WiFi status, settings screens, and diagnostic data.
*/
namespace DebugRenderer
{
// Debug frame functions
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// Trampoline functions for framework callback compatibility
void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// LoRa information display
void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// Memory screen display
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
} // namespace DebugRenderer
} // namespace graphics

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