Compare commits

...
Author SHA1 Message Date
Ben Meadors d84355d15a Add basic BLE Mesh support for ESP32 and NRF52 platforms 2026-03-07 15:22:49 -06:00
Ben Meadors 5d12edbe07 Add convenience userpref for disabling lora during development and testing 2026-03-07 11:13:55 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
398d1adea1 chore(deps): update meshtastic-esp32_https_server digest to b78f12c (#9851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-07 06:51:03 -06:00
Ben Meadors 393cd96ba2 Merge remote-tracking branch 'origin/develop' 2026-03-07 06:39:08 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
6658ec2603 chore(deps): update neopixel to v1.15.4 (#9839)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 19:56:01 -06:00
pdxlocationsandGitHub b941dab900 Add APIPort to native config (#9840) 2026-03-07 08:49:26 +11:00
Ben Meadors ffe6f706f9 Merge remote-tracking branch 'origin/develop' 2026-03-06 10:48:51 -06:00
0ed537a336 Add json file rotation option (#9783)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-06 10:24:32 -06:00
86dad90573 Fix nRF52 AsyncUDP multicast TX/RX race condition causing garbled packets (#9765)
socketSendUDP() calls yield() while waiting for SEND_OK, allowing the
cooperative scheduler to run AsyncUDP::runOnce(). When runOnce() calls
parsePacket() on the same socket during an active send, the multicast
loopback packet arriving from the switch produces a garbled 8-byte RX
header (wrong source IP, wrong payload length), leading to protobuf
decode errors in UdpMulticastHandler.

Add a volatile isSending flag that writeTo() sets around endPacket()
(the only yield point). runOnce() checks this flag and skips
parsePacket() while a send is in progress. Loopback packets stay
buffered in the W5100S RX buffer and are read cleanly on the next
poll cycle.

Also propagate writeTo() return value in UdpMulticastHandler::onSend()
instead of unconditionally returning true.

Made-with: Cursor

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-06 08:38:50 -06:00
Ben MeadorsandGitHub 20db5fdbb7 Fix WisMesh Tap V2 env mess (#9734)
* Fix environment mess with wismesh tap v2

* Address feedback
2026-03-06 06:23:01 -06:00
5b1ea922f3 InkHUD: Nodelist cleanup (#9737)
* Nodelist cleanup

* Trunk fix

* getTextWidth cleanup

Updated the lambda to cache width in textW and reuse it instead of repeatedly calling getTextWidth(text) for unchanged text.

* Putting back hopAway ==0  condition as mentioned relayed signal is missleading

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-06 06:16:34 -06:00
5b94f580dc PPA: Remove Ubuntu 25.04, Add 26.04 (#9789)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-06 06:16:17 -06:00
3d524be965 Update Heltec Tracker v2 to version KCT8103L. (#9822)
* Update Heltec Tracker v2 to version KCT8103L.

* Fixed the issue of inaccurate comments.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-06 06:15:42 -06:00
4ec59ce75d Upgrade trunk (#9835)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-03-06 05:41:56 -06:00
WesselandGitHub cb28c38828 fix(t1000e): reclassify P0.04 as sensor power enable GPIO (#9826)
P0.04 is a digital power-enable pin for the NTC/LUX sensors, not an ADC
input. The old code was calling analogRead() on a floating GPIO that
happened to read ~mid-rail, coincidentally producing reasonable
temperature values.

- Rename T1000X_VCC_PIN to T1000X_SENSOR_EN_PIN and drive it HIGH in
initVariant() for both T1000-E and T1000-S variants
- Read BATTERY_PIN (with ADC_MULTIPLIER) instead, clamped to the 3.0V
LDO output (NTC_REF_VCC) for the NTC resistance calculation
2026-03-06 05:41:37 -06:00
969aefa551 Cardputer Kit (#9540)
* Cardputer Kit
BMI270 WIP

* BMI270 support

* verify that the number of bytes read matches the requested length

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

* trunk'd

* remove excessive logging

* Kick the screen when unsleeping

* Update the st7789 library, and enable displayon and displayoff

* Battery detection

* Default to arrow keys and enter, while in menus.

* Enable Backlight control

* Update src/detect/ScanI2CTwoWire.cpp

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

* updateState method now accepts shouldRequestFocus parameter for better maintainability

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-06 05:34:41 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3f5828c0e9 chore(deps): update docker/setup-buildx-action action to v4 (#9824)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 17:57:10 +11:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
5cf365245e chore(deps): update docker/build-push-action action to v7 (#9832)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 16:28:15 +11:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
fdd17ac75c chore(deps): update docker/metadata-action action to v6 (#9833)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 16:27:48 +11:00
Jonathan BennettandGitHub 9e40c8893f Don't double-blink Thinknode-M1 Power LED while charging (#9829)
* Don't double-blink Thinknode-M1 Power LED while charging

* Drop Double Define
2026-03-05 20:34:31 -06:00
afd5e29bce More RAK6421 work (#9813)
* Add Portduino Enable pins

* Add hat plus custom fields

* Punt on the GPIO device detection for now

* Simplify TX_GAIN_LORA for RAK13302

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-05 08:15:33 -06:00
20f838a61e Add explicit dependency on mklittlefs. (#9708)
This fixes builds in very clean environment with default build target (e.g.
-t buildfs *not* explicitly specified).

Tested: pio run -e heltec-v3

Fixes #9035

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-05 06:55:02 -06:00
Clive BlackledgeandGitHub 58736f5184 fix: add ROUTER_LATE to rebroadcast integrity checks (#9816)
Adds ROUTER_LATE and CLIENT_BASE to preferred rebroadcaster check
(skip unsolicited NodeInfo) and prevents ROUTER_LATE from setting
rebroadcast mode to NONE, which would silently break relaying.
2026-03-05 06:23:17 -06:00
22031c5886 fix: treat ROUTER_LATE like ROUTER for power management and defaults (#9815)
ROUTER_LATE is an infrastructure relay that should use the same power
assumptions, interval defaults, and LoRa wake behavior as ROUTER.
Without this, ROUTER_LATE uses client-class defaults and will not
wake on LoRa activity from deep sleep, making it a broken relay.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-05 06:22:12 -06:00
fe4e75eb66 fix: add ROUTER_LATE and TAK_TRACKER to congestion scaling exemption (#9818)
ROUTER_LATE already has high base intervals and should not be further
scaled by congestion. TAK_TRACKER is a tracker variant that should
skip congestion scaling like TRACKER does.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-05 06:19:45 -06:00
42075da09e Upgrade trunk (#9823)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-03-05 06:17:41 -06:00
Clive BlackledgeandGitHub 6ba82ed5b6 fix: prevent router-like roles from auto-favoriting DM peers (#9821)
ROUTER and ROUTER_LATE should not accumulate favorites by sending DMs.
Also replaces magic number 12 with meshtastic_Config_DeviceConfig_Role_CLIENT_BASE.
2026-03-05 06:00:14 -06:00
935b0504d4 Update protobufs (#9825)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-03-05 06:00:01 -06:00
a8fed3256e fix: add ROUTER_LATE to telemetry impolite role check (#9819)
ROUTER_LATE should be treated as an impolite telemetry role like
ROUTER, responding to multi-hop broadcast requests and using
aggressive send timing without channel utilization checks.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-05 05:59:50 -06:00
Clive BlackledgeandGitHub 1626836a71 fix: add ROUTER_LATE to infrastructure init and config preservation (#9820)
ROUTER_LATE now preserves node_info_broadcast_secs during factory
reset and auto-enables Store & Forward server mode, matching ROUTER
infrastructure behavior.
2026-03-05 05:48:53 -06:00
Larry DoolittleandGitHub 66161da2a7 spelling fixes in .md files (#9810)
9 fixes across 6 files
notably includes github/pull_request_template.md
2026-03-05 08:54:57 +11:00
Ben Meadors 6bbdfe1797 Merge remote-tracking branch 'origin/develop' 2026-03-04 08:24:32 -06:00
7ea28d34b2 Add back FEM LNA mode configuration for LoRa (#9809)
* Add back FEM LNA mode configuration for LoRa

* Update src/mesh/NodeDB.cpp

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

* Update src/modules/AdminModule.cpp

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

* copilot garbage

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 08:23:55 -06:00
Ben MeadorsandGitHub 65bf1630b3 Add some lora parameter clamping logic to coalesce to defaults and enforce some bounds (#9808) 2026-03-04 07:55:34 -06:00
Ben Meadors 3972fe1fd0 Merge remote-tracking branch 'origin/master' into develop 2026-03-04 07:53:12 -06:00
5ced739a57 Add heltec-v4.3 board (#9753)
* Add heltec-v4.3 board

* Modify LNA control display content

* Fix Heltec Tracker v2 FEM control

* Use trunk to fix formatting issues.

* Optimize the fem initialization control logic.

* Update src/mesh/RadioInterface.h change #ifdef to #if

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

* Change LORA_PA_EN to LORA_GC1109_PA_EN.

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

* Remove the NodeDB.h include.

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

* Change tx_gain to a const variable.

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

* Fixed the issue where ARCH_PORTDUINO lacked the NUM_PA_POINTS macro.

* Remove the comment.

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

* Move #pragma once to the first line.

* Remove the FEM LNA control menu.

* Add description for KCT8103L.

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-04 06:27:12 -06:00
b7bf251798 Scaling tweaks (#9653)
* refactor: update throttling factor calculation and add unit tests for scaling behavior

* refactor: adjust throttling factor calculation for improved accuracy in different configurations

* Update src/mesh/Default.h

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

* Update src/mesh/Default.h

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

* refactor: enhance throttling factor calculation and introduce pow_of_2 utility function

* refactor: improve expected ms calculation in unit tests for Default::getConfiguredOrDefaultMsScaled

* refactor: improve scaling logic for routers and sensors in computeExpectedMs function

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 05:59:42 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
90742fc87e chore(deps): update dorny/test-reporter action to v2.6.0 (#9796)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 05:17:53 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
f7f356e2e2 chore(deps): update arduinojson to v6.21.6 (#9788)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 05:17:36 -06:00
1fad7facd6 Upgrade trunk (#9805)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-03-04 05:17:20 -06:00
AustinandGitHub 153ab81a9f Deb: Handle offline builds more gracefully (#9791) 2026-03-04 05:16:45 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
8d73d67246 chore(deps): update docker/setup-qemu-action action to v4 (#9807)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 05:12:32 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
0d46943bb8 chore(deps): update docker/login-action action to v4 (#9806)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 05:12:12 -06:00
Larry DoolittleandGitHub deb45e471c spelling fixes (#9801)
45 corrections in c++ source comments
limited to 27 files in src/gps src/graphics src/input
2026-03-04 16:36:53 +11:00
3601eabbf8 Improve resource cleanup on connection close (and make server API a unique pointer) (#9799)
* Improve resource cleanup on connection close

* Copilot had some good feedback. Let's just make the api a unique pointer

* Update src/mesh/api/ServerAPI.cpp

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

* Copilot stupidly suggesting we call protected methods

* Gotta do it in the superclasses as well

* Fix moar

* Refactor MQTT unit test to ensure proper subscription handling and clear side effects

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-03 13:37:15 -06:00
eb2f3cef89 Update protobufs (#9797)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-03-03 06:43:06 -06:00
85be5965b0 Upgrade trunk (#9795)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-03-03 05:36:09 -06:00
1e34c1ef1b Remove "x" permission bits from some source files (#9794)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-03 05:34:03 -06:00
Thomas GöttgensandGitHub 5f852a1c05 Add ADS1115 ADC to recognition as used on RAK6421 Hat (#9790) 2026-03-03 08:55:53 +01:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
1d90ea6f1d chore(deps): update crazy-max/ghaction-import-gpg action to v7 (#9787)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 08:09:40 +11:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
65eee22396 chore(deps): update github artifact actions (#9767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 08:09:01 +11:00
cb938266c7 Upgrade trunk (#9785)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-03-02 06:45:53 -06:00
Clive BlackledgeandGitHub 8093e2ed5a Bug: Mqtt fix testcase due to immediately sending MapReport (#8872) (#9784)
* Add ESP32 Power Management lessons learned document

Documents our experimentation with ESP-IDF DFS and why it doesn't
work well for Meshtastic (RTOS locks, BLE locks, USB issues).

Proposes simpler alternative: manual setCpuFrequencyMhz() control
with explicit triggers for when to go fast vs slow.

* Added a lambda function to clear startup output in the MQTT unit test to ensure a clean state before and after the MQTT subscription process.
2026-03-02 06:45:42 -06:00
80af726877 avoid memory leak when possibly malformed packet received (#9781)
getByIndex allocates memory and returns dummy channel whenever
chIndex validation fails. Comment implies this may happen
when malformed packet is received. The fix changes implementation
so static dummyChannel is returned in such case.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-28 18:37:41 -06:00
Ben Meadors 3f5dc78744 Update log verbiage to remove GC1109 reference 2026-02-28 17:26:21 -06:00
563adc6aaa enhancement(mesh): remove late packets from tx queue when full (#9779)
* enhance tx queue priority management

In busy environments, especially for ROUTER_LATE role, tx queue
fills very quickly. Delayed packets became late but new packets
to be retransmitted won't be put into the tx queue as old ones
stay there for a very long time (even a minute or more). This change
makes meshtastic prioritize new packets over late packets from tx queue
and allows to remove late packet from back of tx queue when there
is no space for a new one.

* apply copilot recommendation for cast

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-28 17:17:29 -06:00
Ben Meadors 67f806b0a5 Merge remote-tracking branch 'origin/master' into develop 2026-02-28 11:08:15 -06:00
Ben Meadors c7dc536640 Merge remote-tracking branch 'origin/develop' 2026-02-28 09:39:25 -06:00
Ben MeadorsandGitHub 6920a5f8b1 Increase PSRAM malloc threshold from 256 bytes to 2048 bytes (#9758) 2026-02-28 09:34:26 -06:00
2b7a230977 Upgrade trunk (#9752)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-02-28 08:41:34 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
1e16185fdd chore(deps): update platformio/ststm32 to v19.5.0 (#9764)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-28 08:41:21 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
60c2592694 chore(deps): update adafruit dps310 to v1.1.6 (#9763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-28 08:41:01 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
fc0ad4956e chore(deps): update adafruit ahtx0 to v2.0.6 (#9766)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-28 08:40:15 -06:00
Ben MeadorsandGitHub 43aa90fc3f Unlock 0x8B5 register macro guard for SX162 (#9777) 2026-02-28 08:38:08 -06:00
Ben Meadors c28bdbd7e6 Fix mqtt test 2026-02-28 08:32:05 -06:00
857c7b3a3a Don't launch canned message when waking screen or silencing notification (#9762)
* Don't launch canned message when waking screen or silencing notification

* Add screen ifdefs

* Get the #if right

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-27 17:07:03 -06:00
Andrew YongandGitHub 83cac93ca8 fix(MQTT): Send first MapReport as soon as possible (#8872)
* fix(MQTT): First MapReport does not get sent

Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs) is
used to maintain the map reporting interval, but because last_report_to_map has
an initial value of 0, the map report routine does not start until the system
millis() time has passed map_publish_interval_msecs.

Fix this by adding a check that last_report_to_map is not 0.

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

* feat(MQTT): Send MapReporting immediately upon location fix

Do not update last_report_to_map when Map Report is attempted without a valid
location, as this results in waiting up to an hour (or configured Map Report
interval).

That usually happens because most nodes do not keep GPS warm, so GPS usually
locks after the first attempt at Map Report.

This change also results in the log WARNing message getting spammed until a
location is obtained, so remove the message for now.

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

* feat(MQTT): Throttled warning when position is not available for MapReport

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

---------

Signed-off-by: Andrew Yong <me@ndoo.sg>
2026-02-27 05:16:55 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
d34fa3e7df chore(deps): update platformio/nordicnrf52 to v10.11.0 (#9760)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 08:11:00 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
75cb8bb5ea chore(deps): update platformio/espressif32 to v6.13.0 (#9759)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 08:09:44 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2a99cc6a25 chore(deps): update adafruit mlx90614 to v2.1.6 (#9756)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 08:01:28 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
b5f98d1270 chore(deps): update adafruit_tsl2561 to v1.1.3 (#9757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 08:01:03 -06:00
Ben Meadors ef697319da Merge remote-tracking branch 'origin/develop' 2026-02-26 07:53:34 -06:00
Ben MeadorsandGitHub efd68f9fad Fix Bluetooth on RAK Ethernet Gateway by removing MESHTASTIC_EXCLUDE_POWER_FSM from build_flags (#9755) 2026-02-26 07:38:13 -06:00
3a74e049ab Add Transmit history persistence for respecting traffic intervals between reboots (#9748)
* Add transmit history for throttling that persists between reboots

* Fix RAK long press detection to prevent phantom shutdowns from floating pins

* Update test/test_transmit_history/test_main.cpp

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

* Test fixes and placeholder for content handler tests

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-25 20:41:07 -06:00
3b1b308357 platform: nrf52: Fix typo in BLEDfuSecure filename (#9709)
Change file name from BLEDfuScure.cpp to BLEDfuSecure.cpp. Fix filenames
in documentation.

Signed-off-by: Koko <github@kokosoftware.pl>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-25 18:00:02 -06:00
5a068431ed Add GPIO_DETECT_PA portduino config, and support 13302 detection with it (#9741)
* Add GPIO_DETECT_PA portduino config, and support 13302 detection with it.

* Tweak PA detect gpio to use pinMapping

* minor yaml output fixes

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-25 14:18:07 -06:00
25f086a64f Upgrade trunk (#9744)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-02-25 08:30:32 -06:00
Ben MeadorsandGitHub a93d4d7e9d Revert "Fix/rak3401 button (#9668)" (#9747)
This reverts commit a5523b04ef.
2026-02-25 08:26:49 -06:00
Thomas GöttgensandGitHub 54781cf51a hopefully fix remaining cppcheck issues (#9745) 2026-02-25 10:27:21 +01:00
WesselandGitHub ad7d19c317 Remove unused global rIf that shadows locals and fails cppcheck (#9743)
I noticed because my PR failed
2026-02-24 20:57:58 -06:00
Thomas Göttgens 58496e56d2 fail cppcheck on low already. Code quality for the win. 2026-02-24 22:45:38 +01:00
Ben Meadors 2c18a41613 Merge remote-tracking branch 'origin/master' into develop 2026-02-24 14:28:15 -06:00
c65eecc295 Update protobufs (#9739)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-02-24 14:27:23 -06:00
Ben MeadorsandGitHub cac45d9ced Align telemetry broadcast want_response behavior with traceroute (#9717)
* Align telemetry broadcast want_response behavior with traceroute

* Fixes

* Reduce side-effects by making the telemetry modules handle the ignorerequest

* Remove unnecessary ignoreRequest flag

* Try inheriting from MeshModule

* Add exclusion for sensor/router roles and add base telem module
2026-02-24 13:26:47 -06:00
WesselandGitHub 02d42f87d3 Implement 'agc' reset for SX126x & LR11x0 chip families (#9705)
* Implement 'agc' reset for SX126x chip family

There's no actual agc on SX126x chips but you can reset the analog
registers by doing a warm sleep & running calibration.

* Address PR comments & implement for LR11x0 too

* calibrate for configured frequency band

* Gate LR11X0_AGC_RESET
2026-02-24 08:03:25 -06:00
Ben Meadors 9c439f8d0a FIx loophole with telemetry coercion 2026-02-24 06:01:52 -06:00
cf998f03b6 Upgrade trunk (#9731)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-02-24 05:47:18 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
f9d1f2414a chore(deps): update sensorlib to v0.3.4 (#9727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-24 05:46:27 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
13b25c0614 chore(deps): update meshtastic-st7789 digest to 9ee76d6 (#9729)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 20:42:52 -06:00
AustinandGitHub 607b631114 meshtasticd: Add Luckfox Lyra Hat pinmaps (#9730)
Luckfox Lyra Plus + Waveshare Pi Pico Hat
Luckfox Lyra Ultra + wehooper4's "Luckfox Ultra" hat.
2026-02-23 20:42:41 -06:00
99e88bb499 Support mini ePaper S3 Kit (#9335)
* initial draft

* update DC pin

* add ADC channel

* fix e-ink display

* update GxEPD2 reference

* Apply suggestion from @Copilot

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

* Apply suggestion from @Copilot

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

* Apply suggestion from @Copilot

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-23 12:08:24 -06:00
4f84cf011d Upgrade trunk (#9724)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-02-23 05:38:33 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
0726a9f774 chore(deps): update radiolib to v7.6.0 (#9695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-22 18:46:39 +01:00
Ben Meadors f8cc1d6046 Merge remote-tracking branch 'origin/develop' 2026-02-22 06:10:05 -06:00
f615990c00 Limit http connections and add free heap check before allocating for SSL (#9693)
* Reduce maximum concurrent HTTPS connections to save memory

* Add heap check and limit connections to prevent HTTPS connection related crashes

* Use Throttle::isWithinTimespanMs for overflow-safe heap warning throttle

* Update src/mesh/http/WebServer.cpp

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

* Fix double-free heap corruption and parser leak in web server handlers

- Remove double-free in handleFsBrowseStatic, handleNodes, handleScanNetworks:
  JSONValue(const JSONArray&) shallow-copies pointers, so delete value already
  recursively frees all elements. The explicit cleanup loops were deleting the
  same pointers a second time, corrupting the ESP32 heap allocator metadata.
  This is the likely root cause of #8827 (SSL setup failures after uptime).
- Fix memory leak in handleFormUpload: two early-return paths were missing
  delete parser.
- Remove unused global HTTPClient httpClient and its includes.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-21 09:06:17 -06:00
8f81b194d3 BLE Pairing fix (#9701)
* BLE Pairing fix

* gating for consistency

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-20 10:38:07 -06:00
6bc3e31537 Upgrade trunk (#9696)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-02-20 06:45:46 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
15460c8f97 Update meshtastic-GxEPD2 digest to c7eb4c3 (#9694)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-19 17:11:04 -06:00
Ben Meadors a77ac21e0d Trunk 2026-02-19 07:17:50 -06:00
Ben Meadors 2b6d8f6824 Merge branch 'develop' 2026-02-19 07:17:20 -06:00
+19 4c91beeda9 Develop to master (#9618)
* Don't ever define PIN_LED or BLE_LED_INVERTED (#9494)

* Don't ever define PIN_LED

* Deprecate BLE_LED_INVERTED

* Add StatusMessage module and config overrides (#9351)

* Add StatusMessage module and config overrides

* Trunk

* Don't reboot node simply for a StatusMessage config update

* Missed in reviews - fixing send bubble (#9505)

* Prefer EXT_PWR_DETECT pin over chargingVolt to detect power unplugged (#9511)

* Make sure we always return a value in NodeDB::restorePreferences() (#9516)

In case FScom is not defined there is no return statement. This
moves the return outside of the ifdef to make sure a defined
value is returned.

* Inkhud battery icon improvements. (#9513)

* Inkhud battery icon improvements.
Fixes the battery icon draining from the flat side towards the bump, which is backwards from general design language seen on most devices
By request of kr0n05_ on discord, adds the ability to mirror the battery icon which fixes that issue in another way, and is also a common design seen on other devices.

* Remove option for icon mirroring

* Add border + dither to battery to prevent font overlap

* Fix trunk format

* Code cleanup, courtesy of Xaositek.

* Add reply bot module with DM-only responses and rate limiting (#9456)

* Implement Meshtastic reply bot module with ping and status features

Adds a reply bot module that listens for /ping, /hello, and /test commands received via direct messages or broadcasts on the primary channel. The module always replies via direct message to the sender only, reporting hop count, RSSI, and SNR. Per-sender cooldowns are enforced to reduce network spam, and the module can be excluded at build time via a compile flag. Updates include the new module source files and required build configuration changes.

* Update ReplyBotModule.cpp

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

* Update src/modules/ReplyBotModule.h

Match the existing MESHTASTIC_EXCLUDE_* guard pattern so the module is excluded by default.

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

* Update src/modules/ReplyBotModule.cpp

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

* Tidying up

---------

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

* HotFix for ReplyBot - Modules.cpp included and moved configuration.h (#9532)

* Undefine LED_BUILTIN (#9531)

Keep variant in sync with
https://github.com/meshtastic/firmware/commit/df40085

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

* Add agc reset attempt (#8163)

* Add agc reset attempt

* Add radioLibInterface include

* Trunk

* AGC reset don't crash, don't naively call

* Update src/main.cpp

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

* Use Throttle function

---------

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

* Remove unused hmx variable (#9529)

The variable is not used at all in the function, remove it to
silence the compiler warning.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

* Rename LED_PIN to LED_POWER, move handling out of main to dedicated module (#9512)

* Rename LED_PIN to LED_POWER, move handling out of main to dedicated module

* Misc

* Remove errant endif

* Fix hop_limit upgrade detection (#9550)

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

* meshtasticd: Fix install on Fedora 43 (#9556)

* RPM: Include meshtasticd-start.sh (#9561)

* Add Slash Key to VirtualKeyboard (#9563)

Addition of ? and / to the virtual Keyboard via short and long press

* Add support for CW2015 LiPo battery fuel gauge (#9564)

* Add support for CW2015 LiPo battery fuel gauge

* Address Copilot's concerns, minor fixups

* Make LED_POWER blip even in critical battery (#9545)

* Enable FORTIFY and SP for native builds (#9537)

* Enable FORITFY and NX for native builds

meshtasticd does have an executable stack and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and a non-executable stack.

This gives the following improvements on Debian Trixie:

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   13516 Symbols	  No	0		17		./.pio/build/native/meshtasticd

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   13519 Symbols	  Yes	12		20		./.pio/build/native/meshtasticd

Tested with --sim mode I do not get any crashes or similar.

* Enable FORTIFY and NX for native builds

meshtasticd does have an executable stack and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and a non-executable stack.

This gives the following improvements on Debian Trixie:

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   13516 Symbols	  No	0		17		./.pio/build/native/meshtasticd

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   13519 Symbols	  Yes	12		20		./.pio/build/native/meshtasticd

Tested with --sim mode I do not get any crashes or similar.

* Enable FORTIFY and SP for native builds

meshtasticd does have a stack canaries and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and stack canaries.

This gives the following improvements on Debian Trixie:

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   13516 Symbols	  No	0		17		./.pio/build/native/meshtasticd

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   13519 Symbols	  Yes	12		20		./.pio/build/native/meshtasticd

Tested with --sim mode I do not get any crashes or similar.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

* Update built-in documentation for current method of implementation (#9592)

* Refactor logging in ProtobufModule to ensure message details are logged after successful decoding (#9536)

* Automated version bumps (#9604)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* Add missing openocd_target to custom nrf52 boards (#9603)

This stops platformio complaining about `Missing target configuration for me25ls01-4y10td` etc when trying to flash with nrfutil.

* Add sdl libs for native builds (#9595)

* Add sdl libs for native builds

* Alpine try again

* fix some random compiler warnings (#9596)

* Modify the dependency library of v4-tft (#9507)

* BaseUI: Favorite Screen Signal Quality improvement (#9566)

* Favorite Signal Quality improvement

* Show Voltage if node shares it.

* Trunk Fix

* Change Favorite tittle to encase name with Asterisks

* Add Pluggin In condition for Battery Line

* Adjust getUptimeStr Prefixes

* Create isAPIConnected for SharedCommon usage

* Correct leftSideSpacing to account for isAPIConnected

---------

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

* ExternalNotification and StatusLED now call AmbientLighting to update… (#9554)

* ExternalNotification and StatusLED now call AmbientLighting to update RGB LEDs. Add optional heartbeat

* Don't overwrite RGB state if heartbeat is disabled.

* Use the right define

* Remove another .h and make rgb static

* move rgb objects into AmbientLighting class

* Straighten out AmbientLighting Thread object

* Use %f for floats

* Fixes on SCD4X admin comands (#9607)

* Fixes on SCD4X admin comands

* Minor fix in logs for SEN5X

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

* feat/add sfa30 (#9372)

* Move PMSA003I to separate class and update AQ telemetry

* AirQualityTelemetry module not depend on PM sensor presence

* Remove commented line

* Fixes on PMS class

* Add missing warmup period to wakeUp function

* Fixes on compilation for different variants

* Add functions to check for I2C bus speed and set it

* Initial implementation for SFA30Sensor

* Move PMSA003I to separate class and update AQ telemetry

* AirQualityTelemetry module not depend on PM sensor presence

* Remove commented line

* Fixes on PMS class

* Add missing warmup period to wakeUp function

* Fixes on compilation for different variants

* Add functions to check for I2C bus speed and set it

* Add ScreenFonts.h

Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>

* PMSA003I 1st round test

* Fix I2C scan speed

* Fix minor issues and bring back I2C SPEED def

* Remove PMSA003I library as its no longer needed

* Add functional SCD4X

* Fix screen frame for CO2

* Add admin commands to SCD4X class

* Add further admin commands and fixes.

* Remove unused I2C speed functions and cleanup

* Cleanup of SEN5X specific code added from switching branches
* Remove SCAN_I2C_CLOCK_SPEED block as its not needed
* Remove associated functions for setting I2C speed

* Unify build epoch to add flag in platformio-custom.py (#7917)

* Unify build_epoch replacement logic in platformio-custom

* Missed one

* Fix build error in rak_wismesh_tap_v2 (#7905)

In the logs was:
"No screen resolution defined in build_flags. Please define DISPLAY_SIZE."

set according to similar devices.

* Put guards in place around debug heap operations (#7955)

* Put guards in place around debug heap operations

* Add macros to clean up code

* Add pointer as well

* Cleanup

* Fix memory leak in NextHopRouter: always free packet copy when removing from pending

* Formatting

* Only queue 2 client notification

* Merge pull request #7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak

Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap

* Merge pull request #7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak

Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap

* Update protobufs (#7973)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

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

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs

* Trunk

* Trunk

* Static memory pool allocation (#7966)

* Static memory pool

* Initializer

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

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs

---------

Co-authored-by: WillyJL <me@willyjl.dev>

* Portduino dynamic alloc

* Missed

* Drop the limit

* Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (#7977)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Fix json report crashes on esp32 (#7978)

* Tweak maximums

* Fix DRAM overflow on old esp32 targets

* Guard bad time warning logs using GPS_DEBUG (#7897)

In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy.

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

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

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

Reserve this experience for developers using GPS_DEBUG.

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

* Scale probe buffer size based on current baud rate (#7975)

* Scale probe buffer size based on current baud rate

* Throttle bad time validation logging and fix time comparison logic

* Remove comment

* Missed the other instances

* Copy pasta

* Fix GPS gm_mktime memory leak (#7981)

* Fix overflow of time value (#7984)

* Fix overflow of time value

* Revert "Fix overflow of time value"

This reverts commit 0847969201.

* That got boogered up

* Remove PMSA003 include from modules

* Add flag to exclude air quality module

* Rework PMSA003I to align with new I2C scanner

* Reworks AQ telemetry to match new dynamic allocation method
* Adds VBLE_I2C_CLOCK_SPEED build flag for sensors with different I2C speed requirements
* Reworks PMSA003I

* Move add sensor template to separate file

* Split telemetry on screen options

* Add variable I2C clock compile flag

* Added to Seeed Xiao S3 as demo

* Fix drawFrame in AQ module

* Module settings override to i2cScan module function

* Move to CAN_RECLOCK_I2C per architecture

* Add reclock function in TelemetrySensor.cpp
* Add flag in ESP32 common

* Minor fix

* Move I2C reclock function to src/detect

* Fix uninitMemberVar errors and compile issue

* Make sleep, wakeUp functions generic

* Fix STM32 builds

* Add exclude AQ sensor to builds that have environmental sensor excludes
* Add includes to AddI2CSensorTemplate.h

* SEN5X first pass

* WIP Sen5X functions

* Further (non-working) progress in SEN5X

* WIP Sen5X functions

* Changes on SEN5X library - removing pm_env as well

* Small cleanup of SEN5X sensors

* Minor change for SEN5X detection

* Remove dup code

* Enable PM sensor before sending telemetry.

This enables the PM sensor for a predefined period to allow for warmup.

Once telemetry is sent, the sensor shuts down again.

* Small cleanups in SEN5X sensor

* Add dynamic measurement interval for SEN5X

* Only disable SEN5X if enough time after reading.

* Idle for SEN5X on communication error

* Cleanup of logs and remove unnecessary delays

* Small TODO

* Settle on uint16_t for SEN5X PM data

* Make AQTelemetry sensors non-exclusive

* Implementation of cleaning in FS prefs and cleanup

* Remove unnecessary LOGS
* Add cleaning date storage in FS
* Report non-cumulative PN

* Bring back detection code for SEN5X after branch rebase

* Add placeholder for admin message

* Add VOC measurements and persistence (WIP)

* Adds VOC measurements and state
* Still not working on VOC Index persistence
* Should it stay in continuous mode?

* Add one-shot mode config flag to SEN5X

* Add nan checks on sensor data from SEN5X

* Working implementation on VOCState

* Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h)
* Adds conditions for stability and sensor state

* Fixes on VOC state and mode swtiching

* Adds a new RHT/Gas only mode, with 3600s stabilization time
* Fixes the VOCState buffer mismatch
* Fixes SEN50/54/55 model mistake

* Adapt SEN5X to new sensor list structure. Improve reclock.

* Improve reClockI2C conditions for different variants
* Add sleep, wakeUp, pendingForReady, hasSleep functions to PM sensors to save battery
* Add SEN5X

* Fix merge errors

* Small reordering in PMS class for consistency

* If one sensor fails, AQ telemetry still reports data

* Small formatting fix

* Add SEN5X to AQI in ScanI2C

* SCD4X now part of AQ module with template list

* Fixes difference between idle and sleep
* In LowPower, sleep is disabled
* Requires testing for I2C clock comms for commands

* Remove unnecessary import

* Add co2 to serialized AQ metrics

* Add SFA30 with new sensor template in AQ module

* Update library dependencies in platformio.ini

* Fix unitialized variables in SEN5X constructor

* Fix missing import

* Fix uninitMemberVars

* Fix import error for SCD4X

* Fix I2CClock logic

* Fix not reclocking back to 700000Hz

* Fix multiple sensors being read simultaneously

*  The logic in AQ module is different to the one in EnvironmentTelemetryModule.  In Env module, if any sensor fails to getMetrics, no valid flag for the module. This prevents other sensors to report data.

* Fix pending clock change in PMSA003

* Cleanup of SEN5X class

* Exclude AQ sensor from wio-e5 due to flash limitations

* Fix I2C clock change logic

* Make sure clock is always set to needed value

* Fix returns

* Fix trunk

* Fix on condition in reclock

* Fix trunk

* Final SFA30 class implementation

* Add HCHO to screen and improve logs

* Add metrics to mesh packet serializer

* Minor fixes in logs

* OCD tidy up of logs

* Fix sleep function

* Remove old I2C_CLOCK_SPEED code

---------

Co-authored-by: nikl <nikl174@mailbox.org>
Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>
Co-authored-by: Nashui-Yan <yannashui10@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Mike Robbins <mrobbins@alum.mit.edu>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: WillyJL <me@willyjl.dev>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Log rxBad PacketHeaders with more info (id, relay_node) like printPacket, so we can try to match RX errors to other packets in the logs. (#9614)

* Exclude status message module

* fix: zero entire public key array instead of only first byte (#9619)

* Update protobufs (#9621)

* Xiao NRF - define suitable i2c pins for the sub-variants (#8866)

Co-authored-by: Christian Walther <cwalther@gmx.ch>

* Update src/detect/ScanI2C.cpp

Co-authored-by: Wessel <wessel@weebl.me>

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

Co-authored-by: Wessel <wessel@weebl.me>

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

Co-authored-by: Wessel <wessel@weebl.me>

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

Co-authored-by: Wessel <wessel@weebl.me>

* Update src/mesh/NodeDB.cpp

Co-authored-by: Wessel <wessel@weebl.me>

* convert GPS global and some new in gps.cpp to unique_ptr (#9628)

Trying this to see if anything bad happen if I were to replace most raw pointers with unique_ptr.

I didn't used std::make_unique since it is only supported in C++14 and onwards but until we update esp32 to arduino 3.x the ESP32 xtensa chips use a C++11 std.

* replace delete in RedirectablePrint.cpp with std::unique_ptr (#9642)

Is part of the unique_ptr modernization effort.

* replace delete in EInkDynamicDisplay.{cpp,h} with std::unique_ptr (#9643)

Is part of the unique_ptr modernization effort.

* Undefine LED_BUILTIN for Heltec v2 variant (#9647)

* Undefine LED_BUILTIN for Heltec v2 variant

* Undefine LED_BUILTIN for Heltec v2.1 variant

---------

Co-authored-by: Jorropo <jorropo.pgm@gmail.com>

* replace delete in RadioInterface.cpp with std::unique_ptr (#9645)

Is part of the unique_ptr modernization effort.

* fix typo in PIN_GPS_SWITCH (#9648)

Wasn't caught by CI.

* replace delete in CryptoEngine.{cpp,h} with std::unique_ptr (#9649)

Is part of the unique_ptr modernization effort.

* workaround NCP5623 and LP5562 I2C builds (#9652)

Theses two appear to be buggy on r1-neo and nomadstar meteor pro, they rely on Wire.h being included previously to their import.

Idk why other platforms using the same smart LEDs are working while theses ones don't.

This should make CI green on the dev branch.

* replace delete in AudioThread.h with std::unique_ptr (#9651)

Is part of the unique_ptr modernization effort.

* Add USB_MODE=1 for Station G2 (#9660)

* InkHUD: Favorite Map Applet (#9654)

* fix a lot of low level cppcheck warnings (#9623)

* simplify the observer pattern, since all the called functions are const getters.
* use arduino macro over std: for numerical values and refactor local variables in drawScrollbar()
* oh, so Cppcheck actually complained about const pointers not being const.
* slowly getting out of ifdef hell
* fix inkHUD warnings as well
* last 2 check warnings
* git checks should fail on low defects from now on

* Feat/add scd30 (#9609)

* Merge develop into SCD30

* Add SCD30 class

* Fix logging and admin commands

* Minor cleanup and logging improvements

* Minor formatting issue

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

* Improvements on setTemperature

* Fix casting float-uint16_t
* Pass 100 for resetting temperature offset

* Fix issues pointed out by copilot

* Add quick reboot to set interval quicker on scd30

* Change saveState to only happen after boot and minor log changes

* Fix missing semicolon in one shot mode log

---------

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

* fix: respect DontMqttMeBro flag regardless of channel PSK (#9626)

The previous PSK check was broken from its introduction in #4643 —
memcmp was used in boolean context without comparing to 0, inverting
the condition. Since no one noticed for over a year, the PSK-based
filtering provided no practical value. Simplifying to always respect
the sender's preference is both more correct and easier to reason about.

* our firmware action is too clever

Update pio_target and add pio_opts for checks.

* fix detection of SCD30 by checking if the size of the return from a 2 byte register read is correct (#9664)

* fix detection of SCD30 by checking if thee size of the return from a 2 byte register read is correct
fix signedness warning in PMSA003 sensor code.

* Add alternate path for LPS22HB

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

* check EndTransmission for errors and compare returned length to expected value

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

---------

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

* #9623 resolved a local shadow of next_key by converting it to int. (#9665)

* zip a few gitrefs down (#9672)

* InkHUD: Allow non-system applets to subscribe to input events (#9514)

* Allow inkhud user applets to consume inputs with opt-in system
Adds a way for applets to subscribe to input events while keeping it off
by default to preserve compatibility and expected behaviours. Adds
example for use as well.
* Add check for nullptr on getActiveApplet uses
* Remove redundant includes
* Move subscribedInputs to protected
* More consistent naming scheme

* Fake IAQ values on Non-BSEC2 platforms like Platformio and the original ESP32 (#9663)

* BSEC2 Replacement
- add approximation for IAQ to non-BSEC2 platforms.
- Re-add this sensor to ESP32 targets, and refactor env_extra includes.
- Fix C++ 11 compatibility
* Check for gas resistance 0

* ULED_BUILTIN for 9m2ibr_aprs_lora_tracker (#9685)

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Eric Sesterhenn <eric.sesterhenn@x41-dsec.de>
Co-authored-by: Vortetty <33466216+Vortetty@users.noreply.github.com>
Co-authored-by: Mattatat25 <108779801+mattatat25@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Max <rekin.m@gmail.com>
Co-authored-by: Colby Dillion <colby.dillion@pacshealth.com>
Co-authored-by: Austin <vidplace7@gmail.com>
Co-authored-by: Tom <116762865+NomDeTom@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Chloe Bethel <chloe@9net.org>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Quency-D <55523105+Quency-D@users.noreply.github.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Co-authored-by: oscgonfer <oscgonfer@users.noreply.github.com>
Co-authored-by: nikl <nikl174@mailbox.org>
Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>
Co-authored-by: Nashui-Yan <yannashui10@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Mike Robbins <mrobbins@alum.mit.edu>
Co-authored-by: WillyJL <me@willyjl.dev>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Wessel <wessel@weebl.me>
Co-authored-by: Christian Walther <cwalther@gmx.ch>
Co-authored-by: Jorropo <jorropo.pgm@gmail.com>
Co-authored-by: Eric Barch <ericb@ericbarch.com>
Co-authored-by: Clive Blackledge <clive@ansible.org>
2026-02-19 07:16:33 -06:00
d0cf79a991 Upgrade trunk (#9692)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-02-19 06:03:53 -06:00
5408e81de7 Upgrade trunk (#9683)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-02-18 06:44:55 -06:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
357a33f454 chore(deps): update actions/stale action to v10.2.0 (#9669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-17 05:45:54 -06:00
harry-iii-lordandGitHub 4fccda26c6 Concurrency: Modern Periodic wrapper class. (#9501) 2026-02-17 05:44:26 -06:00
f94c01b452 Upgrade trunk (#9671)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-02-17 05:38:13 -06:00
321ccbf5e2 Upgrade trunk (#9661)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-02-16 05:52:46 -06:00
243 changed files with 5433 additions and 625 deletions
+1 -1
View File
@@ -100,7 +100,7 @@ runs:
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}
overwrite: true
+3 -3
View File
@@ -4,16 +4,16 @@
- 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...
is appreciated." This will allow other devs to potentially save you time by not accidentally duplicating work etc...
- Please do not check in files that don't have real changes
- Please do not reformat lines that you didn't have to change the code on
- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor along with the ['Trunk Check' extension](https://marketplace.visualstudio.com/items?itemName=trunk.io) (In beta for windows, WSL2 for the linux version),
- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor along with the ['Trunk Check' extension](https://marketplace.visualstudio.com/items?itemName=trunk.io) (In beta for windows, WSL2 for the Linux version),
because it automatically follows our indentation rules and its auto reformatting will not cause spurious changes to lines.
- If your PR fixes a bug, mention "fixes #bugnum" somewhere in your pull request description.
- If your other co-developers have comments on your PR please tweak as needed.
- Please also enable "Allow edits by maintainers".
- Please do not submit untested code.
- 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 you do not have the affected hardware to test your code changes adequately against regressions, please indicate this, so that contributors and community members can help test your changes.
- If your PR gets accepted you can request a "Contributor" role in the Meshtastic Discord
## 🤝 Attestations
+2 -2
View File
@@ -42,7 +42,7 @@ jobs:
sudo mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
id: gpg
@@ -64,7 +64,7 @@ jobs:
PKG_VERSION: ${{ steps.version.outputs.deb }}
- name: Store binaries as an artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
overwrite: true
+2 -2
View File
@@ -111,7 +111,7 @@ jobs:
echo "</details>" >> $GITHUB_STEP_SUMMARY
- name: Store binaries as an artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
id: upload-firmware
with:
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
@@ -127,7 +127,7 @@ jobs:
release/device-*.bat
- name: Store manifests as an artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
id: upload-manifest
with:
name: manifest-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
+4 -4
View File
@@ -98,7 +98,7 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
path: ./
pattern: firmware-*-*
@@ -111,7 +111,7 @@ jobs:
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -127,7 +127,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
pattern: firmware-*-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -146,7 +146,7 @@ jobs:
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
overwrite: true
+1 -1
View File
@@ -35,8 +35,8 @@ jobs:
series:
- jammy # 22.04 LTS
- noble # 24.04 LTS
- plucky # 25.04
- questing # 25.10
- resolute # 26.04 LTS
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: ppa:meshtastic/daily
+5 -5
View File
@@ -60,16 +60,16 @@ jobs:
- name: Docker login
if: ${{ inputs.push }}
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: meshtastic
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Docker setup
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Sanitize platform string
id: sanitize_platform
@@ -78,7 +78,7 @@ jobs:
- name: Docker tag
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: meshtastic/meshtasticd
tags: |
@@ -86,7 +86,7 @@ jobs:
flavor: latest=false
- name: Docker build and push
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
id: docker_variant
with:
context: .
+3 -3
View File
@@ -139,14 +139,14 @@ jobs:
id: tags
- name: Docker login
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: meshtastic
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
- name: Docker meta (Debian)
id: meta_debian
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: meshtastic/meshtasticd
tags: |
@@ -167,7 +167,7 @@ jobs:
- name: Docker meta (Alpine)
id: meta_alpine
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: meshtastic/meshtasticd
tags: |
+13 -14
View File
@@ -96,7 +96,6 @@ jobs:
pio_platform: ${{ matrix.check.platform }}
pio_env: ${{ matrix.check.board }}
pio_target: check
pio_opts: --fail-on-defect=low
build:
needs: [setup, version]
@@ -178,7 +177,7 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
path: ./
pattern: firmware-${{matrix.arch}}-*
@@ -188,7 +187,7 @@ jobs:
run: ls -R
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -206,7 +205,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -225,7 +224,7 @@ jobs:
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -251,13 +250,13 @@ jobs:
filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head)
fetch-depth: 0
- name: Download the current manifests
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
path: ./manifests-new/
pattern: manifest-*
merge-multiple: true
- name: Upload combined manifests for later commit and global stats crunching.
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
id: upload-manifest
with:
name: manifests-${{ github.sha }}
@@ -325,14 +324,14 @@ jobs:
body: ${{ steps.release_notes.outputs.notes }}
- name: Download source deb
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -356,7 +355,7 @@ jobs:
}' > firmware-${{ needs.version.outputs.long }}.json
- name: Save Release manifest artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: manifest-${{ needs.version.outputs.long }}
overwrite: true
@@ -397,7 +396,7 @@ jobs:
with:
python-version: 3.x
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -414,7 +413,7 @@ jobs:
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -455,14 +454,14 @@ jobs:
python-version: 3.x
- name: Get firmware artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Get manifest artifact
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: manifest-${{ needs.version.outputs.long }}
path: ./publish
+9 -9
View File
@@ -147,7 +147,7 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
path: ./
pattern: firmware-${{matrix.arch}}-*
@@ -160,7 +160,7 @@ jobs:
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -176,7 +176,7 @@ jobs:
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -195,7 +195,7 @@ jobs:
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
@@ -235,14 +235,14 @@ jobs:
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -292,7 +292,7 @@ jobs:
with:
python-version: 3.x
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -309,7 +309,7 @@ jobs:
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
@@ -347,7 +347,7 @@ jobs:
with:
python-version: 3.x
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
+1 -1
View File
@@ -58,7 +58,7 @@ jobs:
id: version
- name: Download artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true
+11 -1
View File
@@ -54,9 +54,19 @@ jobs:
PLATFORMIO_LIBDEPS_DIR: pio/libdeps
PLATFORMIO_PACKAGES_DIR: pio/packages
PLATFORMIO_CORE_DIR: pio/core
PLATFORMIO_SETTING_ENABLE_TELEMETRY: 0
PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL: 3650
PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD: 10240
- name: Mangle platformio cache
# Add "1" to epoch-timestamps of all downloads in the cache.
# This is a hack to prevent internet access at build-time.
run: |
cp pio/core/.cache/downloads/usage.db pio/core/.cache/downloads/usage.db.bak
jq -c 'with_entries(.value |= (. | tostring + "1" | tonumber))' pio/core/.cache/downloads/usage.db.bak > pio/core/.cache/downloads/usage.db
- name: Store binaries as an artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
overwrite: true
+2 -2
View File
@@ -46,7 +46,7 @@ jobs:
sudo apt-get install -y dput
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
id: gpg
@@ -60,7 +60,7 @@ jobs:
id: version
- name: Download artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
merge-multiple: true
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
- name: Download test artifacts
if: needs.native-tests.result != 'skipped'
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: platformio-test-report-${{ steps.version.outputs.long }}
merge-multiple: true
+1 -1
View File
@@ -23,8 +23,8 @@ jobs:
series:
- jammy # 22.04 LTS
- noble # 24.04 LTS
- plucky # 25.04
- questing # 25.10
- resolute # 26.04 LTS
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: |-
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
# step 3
- name: save report as pipeline artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: report.sarif
overwrite: true
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- name: Stale PR+Issues
uses: actions/stale@v10.1.1
uses: actions/stale@v10.2.0
with:
days-before-stale: 45
stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
+7 -7
View File
@@ -59,7 +59,7 @@ jobs:
id: version
- name: Save coverage information
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: always() # run this step even if previous step failed
with:
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}
@@ -94,7 +94,7 @@ jobs:
- name: Save test results
if: always() # run this step even if previous step failed
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: platformio-test-report-${{ steps.version.outputs.long }}
overwrite: true
@@ -108,7 +108,7 @@ jobs:
sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative.
- name: Save coverage information
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: always() # run this step even if previous step failed
with:
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}
@@ -137,20 +137,20 @@ jobs:
id: version
- name: Download test artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: platformio-test-report-${{ steps.version.outputs.long }}
merge-multiple: true
- name: Test Report
uses: dorny/test-reporter@v2.5.0
uses: dorny/test-reporter@v2.6.0
with:
name: PlatformIO Tests
path: testreport.xml
reporter: java-junit
- name: Download coverage artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}
path: code-coverage-report
@@ -163,7 +163,7 @@ jobs:
genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report
- name: Save Code Coverage Report
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: code-coverage-report-${{ steps.version.outputs.long }}
path: code-coverage-report
+11 -11
View File
@@ -4,24 +4,24 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.7.4
ref: v1.7.5
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.501
- renovate@43.10.3
- checkov@3.2.507
- renovate@43.57.0
- prettier@3.8.1
- trufflehog@3.93.3
- trufflehog@3.93.7
- yamllint@1.38.0
- bandit@1.9.3
- trivy@0.69.1
- bandit@1.9.4
- trivy@0.69.3
- taplo@0.10.0
- ruff@0.15.1
- isort@7.0.0
- markdownlint@0.47.0
- ruff@0.15.5
- isort@8.0.1
- markdownlint@0.48.0
- oxipng@10.1.0
- svgo@4.0.0
- actionlint@1.7.10
- svgo@4.0.1
- actionlint@1.7.11
- flake8@7.3.0
- hadolint@2.14.0
- shfmt@3.6.0
+26
View File
@@ -0,0 +1,26 @@
# Lightweight container for running native PlatformIO tests on non-Linux hosts
FROM python:3.14-slim-trixie
ENV DEBIAN_FRONTEND=noninteractive
ENV PIP_ROOT_USER_ACTION=ignore
# hadolint ignore=DL3008
RUN apt-get update && apt-get install --no-install-recommends -y \
g++ git ca-certificates pkg-config \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir platformio==6.1.19 \
&& useradd --create-home --shell /usr/sbin/nologin meshtastic
WORKDIR /firmware
RUN chown -R meshtastic:meshtastic /firmware
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD platformio --version || exit 1
USER meshtastic
# Run tests by default; override with docker run args for specific filters
CMD ["platformio", "test", "-e", "coverage", "-v"]
+1 -1
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 --inline-suppr" $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=low --fail-on-defect=medium --fail-on-defect=high
+2
View File
@@ -187,6 +187,7 @@ Logging:
LogLevel: info # debug, info, warn, error
# TraceFile: /var/log/meshtasticd.json
# JSONFile: /packets.json # File location for JSON output of decoded packets
# JSONFileRotate: 60 # Rotate JSON file every N minutes, or 0 for no rotation
# JSONFilter: position # filter for packets to save to JSON file
# AsciiLogs: true # default if not specified is !isatty() on stdout
@@ -214,3 +215,4 @@ General:
AvailableDirectory: /etc/meshtasticd/available.d/
# MACAddress: AA:BB:CC:DD:EE:FF
# MACAddressSource: eth0
# APIPort: 4403
@@ -6,6 +6,9 @@ Lora:
Reset: 16 # IO4
Busy: 24 # IO5
# Ant_sw: 13 # IO3
Enable_Pins:
- 12
- 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
@@ -4,5 +4,8 @@ Lora:
Reset: 24 # IO4
Busy: 19 # IO5
# Ant_sw: 23 # IO3
Enable_Pins:
- 26
- 23
spidev: spidev0.1
# CS: 7
@@ -0,0 +1,16 @@
Lora:
### RAK13300in Slot 1
Module: sx1262
IRQ: 22 #IO6
Reset: 16 # IO4
Busy: 24 # IO5
# Ant_sw: 13 # IO3
Enable_Pins:
- 12
- 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: 8
TX_GAIN_LORA: [8]
@@ -0,0 +1,12 @@
Lora:
### RAK13300in Slot 2 pins
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
# Ant_sw: 23 # IO3
Enable_Pins:
- 26
- 23
spidev: spidev0.1
# CS: 7
TX_GAIN_LORA: [8]
+5 -1
View File
@@ -5,7 +5,11 @@ Lora:
IRQ: 22 #IO6
Reset: 16 # IO4
Busy: 24 # IO5
# Ant_sw: 13 # IO3
Enable_Pins:
- 12
- 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# GPIO_DETECT_PA: 13
TX_GAIN_LORA: [8]
+16
View File
@@ -0,0 +1,16 @@
# For use with Armbian luckfox-lyra-ultra-w
# Enable overlay 'luckfox-lyra-ultra-w-spi0-cs0-spidev' with armbian-config
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/Luckfox%20Ultra%20Hat
# 1 Watt Lyra Ultra hat
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
CS: 10
IRQ: 5
Busy: 11
Reset: 9
RXen: 14
spidev: spidev0.0 #pins are (CS=10, CLK=8, MOSI=6, MISO=7)
spiSpeed: 2000000
+17
View File
@@ -0,0 +1,17 @@
# For use with Armbian luckfox-lyra-ultra-w
# Enable overlay 'luckfox-lyra-ultra-w-spi0-cs0-spidev' with armbian-config
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/Luckfox%20Ultra%20Hat
# 2 Watt Lyra Ultra hat
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 8
CS: 10
IRQ: 5
Busy: 11
Reset: 9
RXen: 14
spidev: spidev0.0 #pins are (CS=10, CLK=8, MOSI=6, MISO=7)
spiSpeed: 2000000
@@ -0,0 +1,25 @@
# For use with Armbian luckfox-lyra // luckfox-lyra-plus
# Enable overlay 'luckfox-lyra-plus-spi0-cs0_rmio13-spidev' with armbian-config
# Waveshare LoRa HAT for Raspberry Pi Pico
# https://www.waveshare.com/wiki/Pico-LoRa-SX1262
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
spidev: spidev0.0
CS: # GPIO0_B5
pin: 13
gpiochip: 0
line: 13
IRQ: # GPIO1_C2
pin: 50
gpiochip: 1
line: 18
Busy: # GPIO0_B4
pin: 12
gpiochip: 0
line: 12
Reset: # GPIO0_A2
pin: 2
gpiochip: 0
line: 2
+1 -2
View File
@@ -13,8 +13,7 @@ Lora:
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 22
# Reduce output power to improve EMI
NUM_PA_POINTS: 22
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
TX_GAIN_LORA: [12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7]
# Note: This module integrates an additional PA to achieve higher output power.
# The 'power' parameter here does not represent the actual RF output.
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (122 dBm).
+1 -2
View File
@@ -13,8 +13,7 @@ Lora:
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 22
# Reduce output power to improve EMI
NUM_PA_POINTS: 22
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
TX_GAIN_LORA: [12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7]
# Note: This module integrates an additional PA to achieve higher output power.
# The 'power' parameter here does not represent the actual RF output.
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (122 dBm).
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Run native PlatformIO tests inside Docker (for macOS / non-Linux hosts).
#
# Usage:
# ./bin/test-native-docker.sh # run all native tests
# ./bin/test-native-docker.sh -f test_transmit_history # run specific test filter
# ./bin/test-native-docker.sh --rebuild # force rebuild the image
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
IMAGE_NAME="meshtastic-native-test"
REBUILD=false
EXTRA_ARGS=()
for arg in "$@"; do
if [[ "$arg" == "--rebuild" ]]; then
REBUILD=true
else
EXTRA_ARGS+=("$arg")
fi
done
if $REBUILD || ! docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then
echo "Building test image (first run may take a few minutes)..."
docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/Dockerfile.test" "$ROOT_DIR"
fi
# Disable BUILD_EPOCH to avoid full rebuilds between test runs (matches CI)
sed_cmd='s/-DBUILD_EPOCH=$UNIX_TIME/#-DBUILD_EPOCH=$UNIX_TIME/'
# Default: run all tests. Pass extra args (e.g. -f test_transmit_history) through.
if [[ ${#EXTRA_ARGS[@]} -eq 0 ]]; then
CMD=("platformio" "test" "-e" "coverage" "-v")
else
CMD=("platformio" "test" "-e" "coverage" "-v" "${EXTRA_ARGS[@]}")
fi
exec docker run --rm \
-v "$ROOT_DIR:/src:ro" \
"$IMAGE_NAME" \
bash -c "rm -rf /tmp/fw-test && cp -a /src /tmp/fw-test && cd /tmp/fw-test && sed -i '${sed_cmd}' platformio.ini && ${CMD[*]}"
+40
View File
@@ -0,0 +1,40 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default.csv"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_ESP32S3_DEV",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "LilyGo Mini-Epaper-S3 (4 MB Flash, 2MB PSRAM)",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 460800
},
"url": "https://www.lilygo.cc",
"vendor": "LilyGo"
}
+1 -1
View File
@@ -12,6 +12,6 @@ Ex:
- `logo_320x480.png`
- `logo_320x240.png`
This file is copied to `data/boot/logo.png` before filesytem image compilation.
This file is copied to `data/boot/logo.png` before filesystem image compilation.
For additional examples see the [`event/defcon33` branch](https://github.com/meshtastic/firmware/tree/event/defcon33).
+3
View File
@@ -3,6 +3,9 @@ export DEBEMAIL="jbennett@incomsystems.biz"
export PLATFORMIO_LIBDEPS_DIR=pio/libdeps
export PLATFORMIO_PACKAGES_DIR=pio/packages
export PLATFORMIO_CORE_DIR=pio/core
export PLATFORMIO_SETTING_ENABLE_TELEMETRY=0
export PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL=3650
export PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD=10240
# Download libraries to `pio`
platformio pkg install -e native-tft
+4 -1
View File
@@ -9,7 +9,10 @@
PIO_ENV:=\
PLATFORMIO_CORE_DIR=pio/core \
PLATFORMIO_LIBDEPS_DIR=pio/libdeps \
PLATFORMIO_PACKAGES_DIR=pio/packages
PLATFORMIO_PACKAGES_DIR=pio/packages \
PLATFORMIO_SETTING_ENABLE_TELEMETRY=0 \
PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL=3650 \
PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD=10240
# Raspbian armhf builds should be compatible with armv6-hardfloat
# https://www.valvers.com/open-software/raspberry-pi/bare-metal-programming-in-c-part-1/#rpi1-compiler-flags
+625
View File
@@ -0,0 +1,625 @@
# BLE Mesh Implementation Plan
## Overview
Add BLE as a mesh transport alongside LoRa, enabling Meshtastic nodes to relay packets over Bluetooth Low Energy. Follow the same handler pattern used by UDP multicast ([UdpMulticastHandler](src/mesh/udp/UdpMulticastHandler.h)) — a standalone handler class with `onSend()` hooked into `Router::send()` and received packets injected via `router->enqueueReceivedMessage()`. No changes to `RadioInterface` or Router architecture needed.
BLE mesh is useful for:
- Dense indoor deployments where LoRa range is excessive
- Extending mesh reach through devices that lack LoRa hardware
- Hybrid LoRa+BLE networks (gateway nodes bridge both transports)
- Lower latency links between nearby nodes
---
## Architecture
### Pattern: Same as UDP Multicast
```
Router::send()
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
iface->send(p) udpHandler->onSend(p) bleMeshHandler->onSend(p)
(LoRa) (UDP) (BLE mesh)
Router::enqueueReceivedMessage()
┌───────────────────┼───────────────────┐
│ │ │
LoRa RX ISR udpHandler->onReceive() bleMeshHandler->onReceive()
```
The existing UDP pattern ([Router.cpp:378-382](src/mesh/Router.cpp#L378-L382)):
```cpp
#if HAS_UDP_MULTICAST
if (udpHandler && config.network.enabled_protocols & ...) {
udpHandler->onSend(const_cast<meshtastic_MeshPacket *>(p));
}
#endif
```
BLE mesh hooks in identically, right next to it.
---
## Phase 1: Protobuf & Configuration
### 1.1 Transport mechanism enum
**File:** protobufs repo (`mesh.proto`)
Add a new transport type alongside the existing ones:
```protobuf
enum TransportMechanism {
// ... existing 0-7 ...
TRANSPORT_BLE_MESH = 8;
}
```
### 1.2 BLE Mesh configuration
Add BLE mesh config to the device config protobuf:
```protobuf
message BleMeshConfig {
bool enabled = 1;
// Operating mode
enum Mode {
OFF = 0;
ADVERTISE_AND_SCAN = 1; // connectionless (extended adv)
GATT = 2; // connection-based
HYBRID = 3; // both
}
Mode mode = 2;
// Scan/advertising interval in ms (latency vs power tradeoff)
uint32_t interval_ms = 3; // default: 500ms
// TX power level (platform-mapped)
int8_t tx_power = 4; // dBm
// Max BLE mesh peers to track
uint32_t max_peers = 5; // default: 8
}
```
---
## Phase 2: BLE Mesh Handler — Core Class
### 2.1 Base handler (platform-agnostic)
**New file:** `src/mesh/BLEMeshHandler.h`
Follow the `UdpMulticastHandler` pattern — a simple class, not a `RadioInterface`:
```cpp
#pragma once
#if HAS_BLE_MESH
#include "configuration.h"
#include "mesh/Router.h"
class BLEMeshHandler {
public:
BLEMeshHandler() : isRunning(false) {}
virtual void start() = 0;
virtual void stop() = 0;
// Called from Router::send() — broadcast packet over BLE
virtual bool onSend(const meshtastic_MeshPacket *mp) = 0;
protected:
bool isRunning;
// Shared RX path: decode and enqueue into router
void deliverToRouter(const uint8_t *data, size_t len) {
meshtastic_MeshPacket mp;
if (!pb_decode_from_bytes(data, len, &meshtastic_MeshPacket_msg, &mp))
return;
if (mp.which_payload_variant != meshtastic_MeshPacket_encrypted_tag)
return;
mp.transport_mechanism =
meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_MESH;
mp.rx_snr = 0;
mp.rx_rssi = 0;
UniquePacketPoolPacket p = packetPool.allocUniqueCopy(mp);
if (router)
router->enqueueReceivedMessage(p.release());
}
// Shared TX path: encode packet to bytes
size_t encodeForBLE(const meshtastic_MeshPacket *mp, uint8_t *buf, size_t bufLen) {
return pb_encode_to_bytes(buf, bufLen, &meshtastic_MeshPacket_msg, mp);
}
};
#endif // HAS_BLE_MESH
```
### 2.2 Wire format
Same as UDP multicast — protobuf-encoded `meshtastic_MeshPacket` (already encrypted). This means:
- Packets are encoded with `pb_encode_to_bytes()` on TX
- Decoded with `pb_decode_from_bytes()` on RX
- Encryption already applied by Router before `onSend()` is called
- Same deduplication via `PacketHistory` (keyed on `sender, id`)
### 2.3 Echo prevention
Same pattern as UDP ([UdpMulticastHandler.h:102](src/mesh/udp/UdpMulticastHandler.h#L102)):
```cpp
if (mp->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_MESH) {
LOG_ERROR("Attempt to send BLE-sourced packet over BLE");
return false;
}
```
---
## Phase 3: ESP32 Implementation (NimBLE)
### Platform: ESP32, ESP32-S3, ESP32-C3, ESP32-C6
**New files:** `src/nimble/NimbleBLEMesh.h` / `NimbleBLEMesh.cpp`
```cpp
#pragma once
#if HAS_BLE_MESH && defined(ARCH_ESP32)
#include "mesh/BLEMeshHandler.h"
#include "nimble/NimbleBluetooth.h"
class NimbleBLEMesh : public BLEMeshHandler {
public:
void start() override;
void stop() override;
bool onSend(const meshtastic_MeshPacket *mp) override;
private:
// --- Advertising mode ---
void startScanning();
void stopScanning();
static void onScanResult(const ble_gap_disc_desc *desc, void *arg);
void broadcastViaAdv(const uint8_t *data, size_t len);
// --- GATT mode ---
void setupMeshService();
static int onMeshCharWrite(uint16_t conn_handle,
const ble_gatt_error *error,
ble_gatt_attr *attr, void *arg);
void sendViaGATT(const uint8_t *data, size_t len);
// --- Peer tracking ---
struct BLEPeer {
NodeNum nodeNum;
ble_addr_t addr;
int8_t rssi;
uint32_t lastSeenMs;
uint16_t connHandle; // 0 if not connected
};
std::vector<BLEPeer> peers;
void updatePeer(const ble_addr_t &addr, NodeNum nodeNum, int8_t rssi);
void pruneStale();
};
#endif
```
### 3.1 Advertising mode (connectionless)
Encode mesh packets into BLE extended advertisements with Meshtastic manufacturer data.
**TX flow:**
1. `onSend()` called from `Router::send()`
2. Encode packet via `encodeForBLE()`
3. Pack into extended advertisement manufacturer data
4. Broadcast for configurable number of intervals
**RX flow:**
1. NimBLE scan callback fires
2. Check manufacturer ID for Meshtastic magic
3. Extract protobuf bytes
4. Call `deliverToRouter()`
**Advertisement structure:**
```
AD Type: 0xFF (Manufacturer Specific)
Company ID: 0x????? (Meshtastic BLE SIG ID or 0xFFFF)
Mesh Version: 1 byte
Protobuf payload: variable (up to ~244 bytes with extended adv)
```
**Key NimBLE APIs:**
- `ble_gap_ext_adv_set_data()` / `ble_gap_ext_adv_start()` — extended adv TX
- `ble_gap_disc()` or `ble_gap_ext_disc()` — scanning
- Scan callback receives `ble_gap_disc_desc` or `ble_gap_ext_disc_desc`
### 3.2 GATT mode (connection-based)
Add a new GATT service for mesh relay, separate from the phone API service:
**New Mesh Relay Service:**
```
Mesh Relay Service UUID: (new, distinct from phone service in BluetoothCommon.h)
├── MeshTX (WRITE_NO_RSP) — peers write mesh packets to us
├── MeshRX (NOTIFY) — we notify peers of new mesh packets
└── MeshPeer (READ) — our NodeNum for discovery
```
**TX:** Write to MeshTX on all connected peers (broadcast) or specific next-hop peer (directed).
**RX:** MeshTX write callback → `deliverToRouter()`.
### 3.3 Coexistence with phone BLE
Both services registered on the same NimBLE host:
- Phone API service: existing UUIDs ([BluetoothCommon.h](src/BluetoothCommon.h))
- Mesh relay service: new UUIDs
- Separate connection handles for phone vs mesh peers
- Use extended adv set 0 for phone, set 1 for mesh (NimBLE supports multiple adv sets)
- Reserve 1 connection for phone, remaining for mesh peers
### 3.4 ESP32 variant considerations
| Variant | BLE | Extended Adv | Max Conn | Strategy |
| -------- | --- | ------------ | -------- | --------------------------------------- |
| ESP32 | 4.2 | No | 9 | GATT-only (legacy adv too small at 31B) |
| ESP32-S3 | 5.0 | Yes | 9 | Full adv+GATT |
| ESP32-C3 | 5.0 | Yes | 3 | Adv preferred (limited connections) |
| ESP32-C6 | 5.0 | Yes | 9 | Full adv+GATT |
---
## Phase 4: nRF52 Implementation (SoftDevice/Bluefruit)
### Platform: nRF52840 (primary), nRF52833
**New files:** `src/platform/nrf52/NRF52BLEMesh.h` / `NRF52BLEMesh.cpp`
```cpp
#pragma once
#if HAS_BLE_MESH && defined(ARCH_NRF52)
#include "mesh/BLEMeshHandler.h"
#include <bluefruit.h>
class NRF52BLEMesh : public BLEMeshHandler {
public:
void start() override;
void stop() override;
bool onSend(const meshtastic_MeshPacket *mp) override;
private:
// Advertising mode
void startScanning();
static void onScanCallback(ble_gap_evt_adv_report_t *report);
void broadcastViaAdv(const uint8_t *data, size_t len);
// GATT mode
BLEService meshService;
BLECharacteristic meshTxChar;
BLECharacteristic meshRxChar;
void setupMeshService();
static void onMeshWrite(uint16_t connHandle, BLECharacteristic *chr,
uint8_t *data, uint16_t len);
void sendViaGATT(const uint8_t *data, size_t len);
// Peers
struct BLEPeer {
NodeNum nodeNum;
ble_gap_addr_t addr;
int8_t rssi;
uint32_t lastSeenMs;
uint16_t connHandle;
};
std::vector<BLEPeer> peers;
};
#endif
```
### 4.1 Advertising mode
nRF52840 supports BLE 5.0 extended advertisements (up to 247 bytes).
**Bluefruit APIs:**
- `Bluefruit.Scanner.setRxCallback()` — scan result callback
- `Bluefruit.Scanner.start()` / `.stop()` — control scanning
- `Bluefruit.Advertising` — for standard adv
- Raw SoftDevice `sd_ble_gap_adv_set_configure()` if Bluefruit doesn't expose extended adv data
**Implementation:**
- Use Bluefruit Scanner for RX (filter by Meshtastic manufacturer ID)
- For TX, either use Bluefruit Advertising API or drop to raw SoftDevice for extended adv payloads
### 4.2 GATT mode
Same service structure as ESP32, using Bluefruit GATT APIs:
```cpp
meshService = BLEService(MESH_RELAY_SERVICE_UUID);
meshTxChar = BLECharacteristic(MESH_TX_CHAR_UUID);
meshTxChar.setProperties(CHR_PROPS_WRITE_WO_RESP);
meshTxChar.setWriteCallback(onMeshWrite);
meshTxChar.setMaxLen(512);
meshRxChar = BLECharacteristic(MESH_RX_CHAR_UUID);
meshRxChar.setProperties(CHR_PROPS_NOTIFY);
meshRxChar.setMaxLen(512);
```
**Peer connections:**
- nRF52840 SoftDevice S140 supports up to 20 concurrent connections
- Use `Bluefruit.Central` for outbound connections to mesh peers
- Bluefruit supports simultaneous peripheral (phone) + central (mesh peers)
### 4.3 Coexistence with phone BLE
Existing phone service ([NRF52Bluetooth.cpp](src/platform/nrf52/NRF52Bluetooth.cpp)) uses peripheral role:
- Keep phone API service as-is
- Add mesh relay service alongside it
- Scanner shared between peer discovery and mesh RX (filter by service UUID or manufacturer data)
- SoftDevice connection budget: 1 peripheral (phone) + N central (mesh peers)
### 4.4 nRF52 extras
| Feature | nRF52840 | nRF52833 |
| ---------------------- | -------- | -------- |
| Extended Adv | Yes | Yes |
| Max Connections | 20 | 20 |
| RAM per connection | ~1.8KB | ~1.8KB |
| Coded PHY (Long Range) | Yes | Yes |
**Coded PHY:** BLE Coded PHY (125kbps) provides ~4x range vs 1Mbps PHY. Could be used for extended-range BLE mesh links as a configuration option.
---
## Phase 5: Router Integration
### 5.1 Hook into Router::send()
**File:** [src/mesh/Router.cpp](src/mesh/Router.cpp) — add right next to UDP handler:
```cpp
#if HAS_BLE_MESH
if (bleMeshHandler && config.bluetooth.mesh_enabled) {
bleMeshHandler->onSend(p);
}
#endif
```
### 5.2 Global handler instance
**File:** [src/main.h](src/main.h):
```cpp
#ifdef HAS_BLE_MESH
#include "mesh/BLEMeshHandler.h"
extern BLEMeshHandler *bleMeshHandler;
#endif
```
**File:** [src/main.cpp](src/main.cpp):
```cpp
#ifdef HAS_BLE_MESH
BLEMeshHandler *bleMeshHandler = nullptr;
#endif
// In setup():
#ifdef HAS_BLE_MESH
#if defined(ARCH_ESP32)
bleMeshHandler = new NimbleBLEMesh();
#elif defined(ARCH_NRF52)
bleMeshHandler = new NRF52BLEMesh();
#endif
bleMeshHandler->start();
#endif
```
### 5.3 NODENUM_BROADCAST_NO_LORA
Already reserved ([MeshTypes.h:13-14](src/mesh/MeshTypes.h#L13-L14)) and already dropped by LoRa ([RadioLibInterface.cpp:170](src/mesh/RadioLibInterface.cpp#L170)). BLE mesh handler should still send packets addressed to `NODENUM_BROADCAST_NO_LORA` — this is its purpose.
### 5.4 Feature flag
**File:** `src/configuration.h` or per-variant `platformio.ini`:
```cpp
#define HAS_BLE_MESH 1 // Enable BLE mesh transport
```
Disabled by default. Enabled per-board in variant configs for boards with sufficient BLE capability.
---
## Phase 6: Peer Discovery & Management
### 6.1 Discovery methods
1. **Passive scanning**: Listen for Meshtastic manufacturer data in BLE advertisements
2. **Service discovery**: Scan for Mesh Relay Service UUID (GATT mode)
3. **NodeDB integration**: Update `meshtastic_NodeInfoLite` with BLE reachability when a peer is discovered
### 6.2 Peer table
```cpp
struct BLEMeshPeer {
NodeNum nodeNum;
// Platform-specific BLE address (ble_addr_t on ESP32, ble_gap_addr_t on nRF52)
uint8_t addr[6];
uint8_t addrType;
int8_t rssi;
uint32_t lastSeenMs;
uint16_t connHandle; // 0 = not connected
uint8_t failedConnAttempts;
};
```
Max peers: configurable (default 8), bounded by platform connection limits.
### 6.3 Connection policy (GATT mode)
- Auto-connect if RSSI > threshold and slots available
- Disconnect stale peers (no traffic for N minutes)
- Prefer advertising for broadcasts, GATT for directed/reliable
- Exponential backoff on failed connection attempts
---
## Phase 7: Power Management
### 7.1 Scan duty cycling
```
Active scanning: ~15mA (nRF52), ~90mA (ESP32)
Advertising: ~5mA peak per event
Idle: ~0mA additional
Default strategy:
- Scan window: 50ms every 500ms (10% duty cycle)
- ~1.5mA average on nRF52, ~9mA on ESP32
- Configurable via BleMeshConfig.interval_ms
```
### 7.2 Sleep integration
- Light sleep: pause scanning, continue advertising at reduced rate
- Deep sleep: disable BLE mesh entirely
- Hook into existing power management observers
### 7.3 Role-based behavior
| Device Role | BLE Mesh Behavior |
| ----------- | ------------------------------------------- |
| CLIENT | Scan on-demand, advertise when sending |
| CLIENT_MUTE | Receive only, no rebroadcast |
| ROUTER | Continuous scan+advertise, max connections |
| ROUTER_LATE | Delayed rebroadcast (same as LoRa behavior) |
| SENSOR | Minimal: advertise telemetry only |
---
## Phase 8: Testing & Validation
### 8.1 Unit tests
- BLE wire format encode/decode roundtrip
- PacketHistory deduplication across LoRa + BLE
- Peer table management (add, prune, limits)
- Echo prevention (don't re-send BLE-sourced packets over BLE)
### 8.2 Integration tests
- Two ESP32 nodes: BLE-only mesh (no LoRa)
- Two nRF52 nodes: BLE-only mesh
- Three nodes: multi-hop BLE relay
- Hybrid: Node A (LoRa+BLE) <-> Node B (BLE only) <-> Node C (LoRa+BLE)
- Phone API + BLE mesh coexistence
- Power consumption profiling
### 8.3 Compatibility matrix
| Test | ESP32 | ESP32-S3 | ESP32-C3 | ESP32-C6 | nRF52840 |
| ------------- | ----------- | -------- | -------- | -------- | -------- |
| Adv mode | GATT only\* | Yes | Yes | Yes | Yes |
| GATT mode | Yes | Yes | Yes | Yes | Yes |
| Phone coexist | Yes | Yes | Yes | Yes | Yes |
| Multi-hop | Yes | Yes | Yes | Yes | Yes |
\*ESP32 (BLE 4.2): legacy adv limited to 31B, use GATT-only mode
---
## Implementation Order
### Milestone 1: Foundation
1. Protobuf additions (`TRANSPORT_BLE_MESH`, `BleMeshConfig`)
2. `BLEMeshHandler` base class
3. Router hook (alongside UDP handler)
4. Feature flag + main.cpp wiring
### Milestone 2: ESP32 MVP
5. `NimbleBLEMesh` — advertising mode on ESP32-S3
6. Phone BLE coexistence validation
7. Basic two-node BLE mesh test
### Milestone 3: nRF52 MVP
8. `NRF52BLEMesh` — advertising mode
9. Cross-platform interop (ESP32 <-> nRF52 BLE mesh)
### Milestone 4: GATT Mode
10. GATT service + connection management for ESP32
11. GATT service + connection management for nRF52
12. Peer discovery & table management
### Milestone 5: Polish
13. Power management integration
14. Full test matrix
15. ESP32 (BLE 4.2) GATT-only fallback
16. Admin UI / config in phone apps
---
## Risks & Mitigations
| Risk | Impact | Mitigation |
| -------------------------------- | ------------------------- | ---------------------------------------------------------------------------- |
| BLE scan power drain | Battery life | Duty cycling, role-based policy |
| Phone BLE drops during mesh scan | UX regression | Separate adv sets, careful interleaving |
| NimBLE/Bluefruit API differences | Platform code duplication | Thin platform subclasses, shared base logic |
| No extended adv on ESP32 (4.2) | Can't fit packets in adv | GATT-only fallback |
| BLE range (~30-100m) | Coverage gaps | Hybrid LoRa+BLE; Coded PHY on nRF52 |
| Packet storms in dense BLE mesh | Saturation | Existing flood mitigation (PacketHistory, hop_limit, role-based rebroadcast) |
---
## Files to Create
| File | Purpose |
| ------------------------------------- | --------------------------------------------- |
| `src/mesh/BLEMeshHandler.h` | Base handler class (like UdpMulticastHandler) |
| `src/nimble/NimbleBLEMesh.h` | ESP32 BLE mesh header |
| `src/nimble/NimbleBLEMesh.cpp` | ESP32 NimBLE implementation |
| `src/platform/nrf52/NRF52BLEMesh.h` | nRF52 BLE mesh header |
| `src/platform/nrf52/NRF52BLEMesh.cpp` | nRF52 Bluefruit implementation |
## Files to Modify
| File | Change |
| --------------------------------------- | -------------------------------------------------- |
| `src/mesh/Router.cpp` | Add `bleMeshHandler->onSend()` next to UDP handler |
| `src/main.h` | Declare `extern BLEMeshHandler *bleMeshHandler` |
| `src/main.cpp` | Instantiate + start BLE mesh handler |
| `src/configuration.h` | `HAS_BLE_MESH` feature flag |
| Protobufs (`mesh.proto`) | `TRANSPORT_BLE_MESH` enum + `BleMeshConfig` |
| `src/nimble/NimbleBluetooth.cpp` | Coexistence (adv set separation) |
| `src/platform/nrf52/NRF52Bluetooth.cpp` | Coexistence (scanner sharing) |
| Platform `variant.h` / `platformio.ini` | Enable per-board |
+4 -4
View File
@@ -138,7 +138,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library
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
adafruit/Adafruit DPS310@1.1.6
# renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library
adafruit/Adafruit MCP9808 Library@2.0.2
# renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library
@@ -150,7 +150,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
adafruit/Adafruit LIS3DH@1.3.0
# renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0
adafruit/Adafruit AHTX0@2.0.5
adafruit/Adafruit AHTX0@2.0.6
# renovate: datasource=custom.pio depName=Adafruit LSM6DS packageName=adafruit/library/Adafruit LSM6DS
adafruit/Adafruit LSM6DS@4.7.4
# renovate: datasource=custom.pio depName=Adafruit TSL2591 packageName=adafruit/library/Adafruit TSL2591 Library
@@ -158,7 +158,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=EmotiBit MLX90632 packageName=emotibit/library/EmotiBit MLX90632
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
adafruit/Adafruit MLX90614 Library@2.1.6
# renovate: datasource=git-refs depName=INA3221 packageName=https://github.com/sgtwilko/INA3221 gitBranch=FixOverflow
https://github.com/sgtwilko/INA3221/archive/bb03d7e9bfcc74fc798838a54f4f99738f29fc6a.zip
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
@@ -180,7 +180,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
dfrobot/DFRobot_BMM150@1.0.0
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
adafruit/Adafruit TSL2561@1.1.2
adafruit/Adafruit TSL2561@1.1.3
# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/library/BH1750_WE
wollewald/BH1750_WE@1.1.10
+1 -3
View File
@@ -51,9 +51,7 @@ class AudioThread : public concurrency::OSThread
i2sRtttl = nullptr;
}
if (rtttlFile != nullptr) {
rtttlFile = nullptr;
}
rtttlFile = nullptr;
setCPUFast(false);
#ifdef T_LORA_PAGER
+8 -2
View File
@@ -38,7 +38,10 @@ static bool isPowered()
return true;
#endif
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
bool isRouter = ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)
? 1
: 0);
// If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON
// We assume routers might be powered all the time, but from a low current (solar) source
@@ -262,7 +265,10 @@ Fsm powerFSM(&stateBOOT);
void PowerFSM_setup()
{
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
bool isRouter = ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)
? 1
: 0);
bool hasPower = isPowered();
LOG_INFO("PowerFSM init, USB power=%d", hasPower ? 1 : 0);
+2
View File
@@ -35,6 +35,8 @@ void consoleInit()
#if defined(SERIAL_HAS_ON_RECEIVE)
// onReceive does only exist for HardwareSerial not for USB CDC serial
Port.onReceive([sc]() { sc->rxInt(); });
#else
(void)sc;
#endif
DEBUG_PORT.rpInit(); // Simply sets up semaphore
}
+11 -6
View File
@@ -1,24 +1,29 @@
#pragma once
#include <functional>
#include <utility>
#include "concurrency/OSThread.h"
namespace concurrency
{
/**
* @brief Periodically invoke a callback. This just provides C-style callback conventions
* rather than a virtual function - FIXME, remove?
* @brief Periodically invoke a callback.
* Supports both legacy function pointers and modern callables.
*/
class Periodic : public OSThread
{
int32_t (*callback)();
public:
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
Periodic(const char *name, int32_t (*_callback)()) : OSThread(name), callback(_callback) {}
Periodic(const char *name, int32_t (*cb)()) : OSThread(name), callback(cb) {}
Periodic(const char *name, std::function<int32_t()> cb) : OSThread(name), callback(std::move(cb)) {}
protected:
int32_t runOnce() override { return callback(); }
int32_t runOnce() override { return callback ? callback() : 0; }
private:
std::function<int32_t()> callback;
};
} // namespace concurrency
+12
View File
@@ -149,6 +149,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
#endif
#ifdef USE_KCT8103L_PA
// Power Amps are often non-linear, so we can use an array of values for the power curve
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 12, 12, 11, 10, 9, 8, 7
#endif
#ifdef RAK13302
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8
@@ -163,6 +169,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define TX_GAIN_LORA 0
#endif
#ifndef HAS_LORA_FEM
#define HAS_LORA_FEM 0
#endif
// -----------------------------------------------------------------------------
// Feature toggles
// -----------------------------------------------------------------------------
@@ -261,6 +271,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BHI260AP_ADDR 0x28
#define BMM150_ADDR 0x13
#define DA217_ADDR 0x26
#define BMI270_ADDR 0x68
#define BMI270_ADDR_ALT 0x69
// -----------------------------------------------------------------------------
// LED
+2 -2
View File
@@ -37,8 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150};
return firstOfOrNONE(9, types);
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150, BMI270};
return firstOfOrNONE(10, types);
}
ScanI2C::FoundDevice ScanI2C::firstAQI() const
+3 -1
View File
@@ -89,10 +89,12 @@ class ScanI2C
DA217,
CHSC6X,
CST226SE,
BMI270,
SEN5X,
SFA30,
CW2015,
SCD30
SCD30,
ADS1115
} DeviceType;
// typedef uint8_t DeviceAddress;
+21 -7
View File
@@ -652,9 +652,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
case ICM20948_ADDR: // same as BMX160_ADDR and SEN5X_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
// ICM20948 Register check
case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT, and SEN5X_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
#ifdef HAS_ICM20948
type = ICM20948;
@@ -665,6 +664,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = ICM20948;
logFoundDevice("ICM20948", (uint8_t)addr.address);
break;
} else if (registerValue == 0x24) {
type = BMI270;
logFoundDevice("BMI270", (uint8_t)addr.address);
break;
} else if (registerValue == 0xD8) { // BMX160 chip ID at register 0x00
type = BMX160;
logFoundDevice("BMX160", (uint8_t)addr.address);
break;
} else {
String prod = "";
prod = readSEN5xProductName(i2cBus, addr.address);
@@ -718,11 +725,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
if (len == 5 && memcmp(expectedInfo, info, len) == 0) {
LOG_INFO("NXP SE050 crypto chip found");
type = NXP_SE050;
} else {
LOG_INFO("FT6336U touchscreen found");
type = FT6336U;
break;
}
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2);
if (registerValue == 0x8583 || registerValue == 0x8580) {
type = ADS1115;
logFoundDevice("ADS1115 ADC", (uint8_t)addr.address);
break;
}
LOG_INFO("FT6336U touchscreen found");
type = FT6336U;
break;
}
+6 -6
View File
@@ -93,7 +93,7 @@ static const char *getGPSPowerStateString(GPSPowerState state)
#ifdef PIN_GPS_SWITCH
// If we have a hardware switch, define a periodic watcher outside of the GPS runOnce thread, since this can be sleeping
// idefinitely
// indefinitely
int lastState = LOW;
bool firstrun = true;
@@ -586,14 +586,14 @@ bool GPS::setup()
_serial_gps->write("$PMTK301,2*2E\r\n");
delay(250);
} else if (gnssModel == GNSS_MODEL_ATGM336H) {
// Set the intial configuration of the device - these _should_ work for most AT6558 devices
// Set the initial configuration of the device - these _should_ work for most AT6558 devices
msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
_serial_gps->write(UBXscratch, msglen);
if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) {
LOG_WARN("ATGM336H: Could not set Config");
}
// Set the update frequence to 1Hz
// Set the update frequency to 1Hz
msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ);
_serial_gps->write(UBXscratch, msglen);
if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) {
@@ -700,7 +700,7 @@ bool GPS::setup()
} else { // 8,9
LOG_INFO("GPS+SBAS+GLONASS+Galileo configured");
}
// Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next
// Documentation say, we need wait at least 0.5s after reconfiguration of GNSS module, before sending next
// commands for the M8 it tends to be more... 1 sec should be enough ;>)
delay(1000);
}
@@ -733,7 +733,7 @@ bool GPS::setup()
SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "enable powersave for GPS", 500);
SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
// For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats.
// For M8 we want to enable NMEA version 4.10 so we can see the additional sats.
if (gnssModel == GNSS_MODEL_UBLOX8) {
clearBuffer();
SEND_UBX_PACKET(0x06, 0x17, _message_NMEA, "enable NMEA 4.10", 500);
@@ -1211,7 +1211,7 @@ int32_t GPS::runOnce()
return disable(); // This should trigger when we have a fixed position, and get that first position
// 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms
// if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake.
// if not awake we can run super infrequently (once every 5 secs?) to see if we need to wake.
return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
}
+5 -5
View File
@@ -12,7 +12,7 @@ GeoCoord::GeoCoord(int32_t lat, int32_t lon, int32_t alt) : _latitude(lat), _lon
GeoCoord::GeoCoord(float lat, float lon, int32_t alt) : _altitude(alt)
{
// Change decimial representation to int32_t. I.e., 12.345 becomes 123450000
// Change decimal representation to int32_t. I.e., 12.345 becomes 123450000
_latitude = int32_t(lat * 1e+7);
_longitude = int32_t(lon * 1e+7);
GeoCoord::setCoords();
@@ -20,7 +20,7 @@ GeoCoord::GeoCoord(float lat, float lon, int32_t alt) : _altitude(alt)
GeoCoord::GeoCoord(double lat, double lon, int32_t alt) : _altitude(alt)
{
// Change decimial representation to int32_t. I.e., 12.345 becomes 123450000
// Change decimal representation to int32_t. I.e., 12.345 becomes 123450000
_latitude = int32_t(lat * 1e+7);
_longitude = int32_t(lon * 1e+7);
GeoCoord::setCoords();
@@ -467,10 +467,10 @@ int32_t GeoCoord::bearingTo(const GeoCoord &pointB)
}
/**
* Create a new point bassed on the passed in poin
* Create a new point based on the passed-in point
* Ported from http://www.edwilliams.org/avform147.htm#LL
* @param bearing
* The bearing in raidans
* The bearing in radians
* @param range_meters
* range in meters
* @return GeoCoord object of point at bearing and range from initial point
@@ -593,4 +593,4 @@ double GeoCoord::toRadians(double deg)
double GeoCoord::toDegrees(double r)
{
return r * 180 / PI;
}
}
+2 -2
View File
@@ -223,7 +223,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
// This delta value works on all platforms
timeStartMsec = now;
zeroOffsetSecs = tv->tv_sec;
// If this platform has a setable RTC, set it
// If this platform has a settable RTC, set it
#ifdef RV3028_RTC
if (rtc_found.address == RV3028_RTC) {
Melopero_RV3028 rtc;
@@ -402,7 +402,7 @@ time_t gm_mktime(const struct tm *tm)
#if !MESHTASTIC_EXCLUDE_TZ
time_t result = 0;
// First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
// First, get us to the start of tm->year, by calculating the number of days since the Unix epoch.
int year = 1900 + tm->tm_year; // tm_year is years since 1900
int year_minus_one = year - 1;
int days_before_this_year = 0;
+1 -1
View File
@@ -37,7 +37,7 @@ static const uint8_t _message_CAS_CFG_RATE_1HZ[] = {
// CFG-NAVX (0x06, 0x07)
// Initial ATGM33H-5N configuration, Updates for Dynamic Mode, Fix Mode, and SV system
// Qwirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
// Quirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
// and use GPS+BDS+GLONASS iff the correct CFG_NAVX command is used.
static const uint8_t _message_CAS_CFG_NAVX_CONF[] = {
0x03, 0x01, 0x00, 0x00, // Update Mask: Dynamic Mode, Fix Mode, Nav Settings
+6 -6
View File
@@ -57,7 +57,7 @@ static const uint8_t _message_CFG_PM2[] PROGMEM = {
0x00, 0x00, 0x00, 0x00 // 0x64, 0x40, 0x01, 0x00 // reserved 11
};
// Constallation setup, none required for Neo-6
// Constellation setup, none required for Neo-6
// For Neo-7 GPS & SBAS
static const uint8_t _message_GNSS_7[] = {
@@ -157,7 +157,7 @@ static const uint8_t _message_NAVX5[] = {
0x00, 0x00, 0x00, 0x00, // Reserved 9
0x00, // Reserved 10
0x00, // Reserved 11
0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true)
0x00, // usePPP (Precise Point Positioning) (0 = false, 1 = true)
0x01, // useAOP (AssistNow Autonomous configuration) = 1 (enabled)
0x00, // Reserved 12
0x00, // Reserved 13
@@ -185,7 +185,7 @@ static const uint8_t _message_NAVX5_8[] = {
0x00, // Reserved 4
0x00, 0x00, // Reserved 5
0x00, 0x00, // Reserved 6
0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true)
0x00, // usePPP (Precise Point Positioning) (0 = false, 1 = true)
0x01, // aopCfg (AssistNow Autonomous configuration) = 1 (enabled)
0x00, 0x00, // Reserved 7
0x00, 0x00, // aopOrbMaxErr = 0 to reset to firmware default
@@ -314,7 +314,7 @@ static const uint8_t _message_DISABLE_TXT_INFO[] = {
// This command applies to M8 products
static const uint8_t _message_PMS[] = {
0x00, // Version (0)
0x03, // Power setup value 3 = Agresssive 1Hz
0x03, // Power setup value 3 = Agressive 1Hz
0x00, 0x00, // period: not applicable, set to 0
0x00, 0x00, // onTime: not applicable, set to 0
0x00, 0x00 // reserved, generated by u-center
@@ -337,7 +337,7 @@ static const uint8_t _message_SAVE_10[] = {
// As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
// BBR will survive a restart, and power off for a while, but modules with small backup
// batteries or super caps will not retain the config for a long power off time.
// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast aquisition after
// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast acquisition after
// sleep
// VALSET Commands for M10
@@ -462,7 +462,7 @@ Default GNSS configuration is: GPS, Galileo, BDS B1l, with QZSS and SBAS enabled
The PMREQ puts the receiver to sleep and wakeup re-acquires really fast and seems to not need
the PM config. Lets try without it.
PMREQ sort of works with SBAS, but the awake time is too short to re-acquire any SBAS sats.
The defination of "Got Fix" doesn't seem to include SBAS. Much more too this...
The definition of "Got Fix" doesn't seem to include SBAS. Much more too this...
Even if it was, it can take minutes (up to 12.5),
even under good sat visibility conditions to re-acquire the SBAS data.
+12 -1
View File
@@ -101,7 +101,7 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
return true;
}
// End the update process - virtual method, overriden in derived class
// End the update process - virtual method, overridden in derived class
void EInkDisplay::endUpdate()
{
// Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
@@ -259,6 +259,17 @@ bool EInkDisplay::connect()
adafruitDisplay->setRotation(3);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
}
#elif defined(MINI_EPAPER_S3)
spi1 = new SPIClass(HSPI);
spi1->begin(PIN_SPI1_SCK, PIN_SPI1_MISO, PIN_SPI1_MOSI, PIN_EINK_CS);
// Create GxEPD2 objects
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *spi1);
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
// Init GxEPD2
adafruitDisplay->init();
adafruitDisplay->setRotation(1);
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
// Detect display model, before starting SPI
+2 -1
View File
@@ -93,7 +93,8 @@ class EInkDisplay : public OLEDDisplay
SPIClass *hspi = NULL;
#endif
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK) || \
defined(MINI_EPAPER_S3)
SPIClass *spi1 = NULL;
#endif
+3 -3
View File
@@ -95,7 +95,7 @@ void EInkDynamicDisplay::adjustRefreshCounters()
// Trigger the display update by calling base class
bool EInkDynamicDisplay::update()
{
// Detemine the refresh mode to use, and start the update
// Determine the refresh mode to use, and start the update
bool refreshApproved = determineMode();
if (refreshApproved) {
EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
@@ -317,7 +317,7 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious()
LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x", frameFlags);
}
// Have too many fast-refreshes occured consecutively, since last full refresh?
// Have too many fast-refreshes occurred consecutively, since last full refresh?
void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
{
// If a decision was already reached, don't run the check
@@ -561,4 +561,4 @@ void EInkDynamicDisplay::awaitRefresh()
}
#endif // HAS_EINK_ASYNCFULL
#endif // USE_EINK_DYNAMICDISPLAY
#endif // USE_EINK_DYNAMICDISPLAY
+15 -2
View File
@@ -433,12 +433,15 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif
#if defined(MUZI_BASE)
// some screens seem to need a kick in the pants to turn back on
#if defined(MUZI_BASE) || defined(M5STACK_CARDPUTER_ADV)
dispdev->init();
dispdev->setBrightness(brightness);
dispdev->flipScreenVertically();
dispdev->resetDisplay();
#ifdef SCREEN_12V_ENABLE
digitalWrite(SCREEN_12V_ENABLE, HIGH);
#endif
delay(100);
#endif
#if !ARCH_PORTDUINO
@@ -462,9 +465,11 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2)
ui->init();
#endif
#ifdef USE_ST7789
#if defined(USE_ST7789) && defined(VTFT_LEDA)
#ifdef VTFT_CTRL
pinMode(VTFT_CTRL, OUTPUT);
digitalWrite(VTFT_CTRL, LOW);
#endif
ui->init();
#ifdef ESP_PLATFORM
analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT);
@@ -506,8 +511,12 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#ifdef USE_ST7789
SPI1.end();
#if defined(ARCH_ESP32)
#ifdef VTFT_LEDA
pinMode(VTFT_LEDA, ANALOG);
#endif
#ifdef VTFT_CTRL
pinMode(VTFT_CTRL, ANALOG);
#endif
pinMode(ST7789_RESET, ANALOG);
pinMode(ST7789_RS, ANALOG);
pinMode(ST7789_NSS, ANALOG);
@@ -875,6 +884,10 @@ int32_t Screen::runOnce()
break;
case Cmd::STOP_ALERT_FRAME:
NotificationRenderer::pauseBanner = false;
// Return from one-off alert mode back to regular frames.
if (!showingNormalScreen && NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
setFrames();
}
break;
case Cmd::STOP_BOOT_SCREEN:
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
+4
View File
@@ -765,7 +765,11 @@ class Screen : public concurrency::OSThread
DebugInfo debugInfo;
/// Display device
#ifdef USE_ST7789
ST7789Spi *dispdev;
#else
OLEDDisplay *dispdev;
#endif
/// UI helper for rendering to frames and switching between them
OLEDDisplayUi *ui;
+2 -2
View File
@@ -1209,8 +1209,8 @@ void TFTDisplay::display(bool fromBlank)
bool somethingChanged = false;
// Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step
colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8);
colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8);
colorTftMesh = __builtin_bswap16(TFT_MESH);
colorTftBlack = __builtin_bswap16(TFT_BLACK);
y = 0;
while (y < displayHeight) {
+3 -3
View File
@@ -43,7 +43,7 @@ InputEvent NotificationRenderer::inEvent;
int8_t NotificationRenderer::curSelected = 0;
char NotificationRenderer::alertBannerMessage[256] = {0};
uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever
uint8_t NotificationRenderer::alertBannerOptions = 0; // last x lines are seelctable options
uint8_t NotificationRenderer::alertBannerOptions = 0; // last x lines are selectable options
const char **NotificationRenderer::optionsArrayPtr = nullptr;
const int *NotificationRenderer::optionsEnumPtr = nullptr;
std::function<void(int)> NotificationRenderer::alertBannerCallback = NULL;
@@ -95,7 +95,7 @@ void NotificationRenderer::resetBanner()
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent.kbchar = 0;
curSelected = 0;
alertBannerOptions = 0; // last x lines are seelctable options
alertBannerOptions = 0; // last x lines are selectable options
optionsArrayPtr = nullptr;
optionsEnumPtr = nullptr;
alertBannerCallback = NULL;
@@ -781,4 +781,4 @@ void NotificationRenderer::showKeyboardMessagePopupWithTitle(const char *title,
}
} // namespace graphics
#endif
#endif
+1 -1
View File
@@ -22,7 +22,7 @@ class NotificationRenderer
static uint32_t alertBannerUntil; // 0 is a special case meaning forever
static const char **optionsArrayPtr;
static const int *optionsEnumPtr;
static uint8_t alertBannerOptions; // last x lines are seelctable options
static uint8_t alertBannerOptions; // last x lines are selectable options
static std::function<void(int)> alertBannerCallback;
static uint32_t numDigits;
static uint32_t currentNumber;
@@ -37,8 +37,8 @@ class DEPG0213BNS800 : public SSD16XX
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
void finalizeUpdate() override; // Only overriden for a slight optimization
void finalizeUpdate() override; // Only overridden for a slight optimization
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -35,8 +35,8 @@ class DEPG0290BNS800 : public SSD16XX
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
void finalizeUpdate() override; // Only overriden for a slight optimization
void finalizeUpdate() override; // Only overridden for a slight optimization
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+1 -1
View File
@@ -19,7 +19,7 @@ void setupNicheGraphics()
SPIClass *hspi = new SPIClass(HSPI);
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
// Setup Enk driver
// Setup EInk driver
Drivers::EInk *driver = new Drivers::DEPG0290BNS800;
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY);
+2 -2
View File
@@ -3,7 +3,7 @@
/*
Base class for InkHUD applets
Must be overriden
Must be overridden
An applet is one "program" which may show info on the display.
@@ -208,4 +208,4 @@ class Applet : public GFX
}; // namespace NicheGraphics::InkHUD
#endif
#endif
@@ -525,7 +525,7 @@ void InkHUD::MapApplet::calculateAllMarkers()
}
// Determine the conversion factor between metres, and pixels on screen
// May be overriden by derived applet, if custom scale required (fixed map size?)
// May be overridden by derived applet, if custom scale required (fixed map size?)
void InkHUD::MapApplet::calculateMapScale()
{
// Aspect ratio of map and screen
@@ -555,4 +555,4 @@ void InkHUD::MapApplet::drawCross(int16_t x, int16_t y, uint8_t size)
drawLine(x0, y1, x1, y0, BLACK);
}
#endif
#endif
@@ -120,9 +120,26 @@ void InkHUD::NodeListApplet::onRender(bool full)
// Draw the main node list
// ========================
// Imaginary vertical line dividing left-side and right-side info
// Long-name will crop here
const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops");
// Leave a small gutter between long-name text and right-side card content
constexpr uint8_t rightContentGap = 2;
// Truncate with trailing "...", sized using the current font.
auto ellipsizeToWidth = [this](std::string text, uint16_t maxWidth) {
constexpr const char *ellipsis = "...";
const uint16_t ellipsisW = getTextWidth(ellipsis);
uint16_t textW = getTextWidth(text);
if (maxWidth == 0)
return std::string();
if (textW <= maxWidth)
return text;
if (ellipsisW > maxWidth)
return std::string();
while (!text.empty() && (textW + ellipsisW > maxWidth)) {
text.pop_back();
textW = getTextWidth(text);
}
return text + ellipsis;
};
// Y value (top) of the current card. Increases as we draw.
uint16_t cardTopY = headerDivY + padDivH;
@@ -141,7 +158,7 @@ void InkHUD::NodeListApplet::onRender(bool full)
SignalStrength &signal = card->signal;
std::string longName; // handled below
std::string shortName; // handled below
std::string distance; // handled below;
std::string distance; // handled below
const uint8_t &hopsAway = card->hopsAway;
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
@@ -185,41 +202,49 @@ void InkHUD::NodeListApplet::onRender(bool full)
setFont(fontMedium);
printAt(0, lineAY, shortName, LEFT, MIDDLE);
// Print the distance
// Right-side labels and long name are rendered in small font.
setFont(fontSmall);
printAt(width() - 1, lineBY, distance, RIGHT, MIDDLE);
uint16_t rightContentW = 0;
// If we have a direct connection to the node, draw the signal indicator
// Bottom row right: distance.
if (!distance.empty()) {
rightContentW = std::max(rightContentW, getTextWidth(distance));
printAt(width() - 1, lineBY, distance, RIGHT, MIDDLE);
}
// Top row right: direct-link signal only.
if (hopsAway == 0 && signal != SIGNAL_UNKNOWN) {
uint16_t signalW = getTextWidth("Xkm"); // Indicator should be similar width to distance label
uint16_t signalW = getTextWidth("Xkm"); // Indicator width tuned to a short right-side label
uint16_t signalH = fontMedium.lineHeight() * 0.75;
int16_t signalY = lineAY + (fontMedium.lineHeight() / 2) - (fontMedium.lineHeight() * 0.75);
int16_t signalX = width() - signalW;
rightContentW = std::max(rightContentW, signalW);
drawSignalIndicator(signalX, signalY, signalW, signalH, signal);
}
// Otherwise, print "hops away" info, if available
else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) {
std::string hopString = to_string(node->hops_away);
hopString += " Hop";
if (node->hops_away != 1)
hopString += "s"; // Append s for "Hops", rather than "Hop"
} else if (hopsAway != CardInfo::HOPS_UNKNOWN) {
std::string hopString = to_string(hopsAway) + (hopsAway == 1 ? " Hop" : " Hops");
rightContentW = std::max(rightContentW, getTextWidth(hopString));
printAt(width() - 1, lineAY, hopString, RIGHT, MIDDLE);
}
// Print the long name, cropping to prevent overflow onto the right-side info
setCrop(0, 0, dividerX - 1, height());
printAt(0, lineBY, longName, LEFT, MIDDLE);
// Give long names as much room as possible while still avoiding right side signal and hop space
const uint16_t longNameMaxW =
(rightContentW + rightContentGap < width()) ? (width() - rightContentW - rightContentGap) : 0;
const std::string longNameShown = ellipsizeToWidth(longName, longNameMaxW);
// GFX effect: "hatch" the right edge of longName area
// If a longName has been cropped, it will appear to fade out,
// creating a soft barrier with the right-side info
const int16_t hatchLeft = dividerX - 1 - (fontSmall.lineHeight());
const int16_t hatchWidth = fontSmall.lineHeight();
hatchRegion(hatchLeft, cardTopY, hatchWidth, cardH, 2, WHITE);
// Safety crop
setCrop(0, cardTopY, longNameMaxW, cardH);
printAt(0, lineBY, longNameShown, LEFT, MIDDLE);
resetCrop();
// Draw separator between cards
const int16_t separatorY = cardTopY + cardH - 1;
if (separatorY < height() - 1 && (card + 1) != cards.end()) {
for (int16_t xSep = 0; xSep < width(); xSep += 2)
drawPixel(xSep, separatorY, BLACK);
}
// Prepare to draw the next card
resetCrop();
cardTopY += cardH;
// Once we've run out of screen, stop drawing cards
@@ -288,4 +313,4 @@ void InkHUD::NodeListApplet::drawSignalIndicator(int16_t x, int16_t y, uint16_t
}
}
#endif
#endif
@@ -65,10 +65,10 @@ class NodeListApplet : public Applet, public MeshModule
// Card Dimensions
// - for rendering and for maxCards calc
uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
uint8_t cardMarginH = 1; // Gap between cards (minimal to fit more rows)
uint16_t cardH = fontMedium.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card
};
} // namespace NicheGraphics::InkHUD
#endif
#endif
@@ -6,7 +6,7 @@ using namespace NicheGraphics;
InkHUD::BatteryIconApplet::BatteryIconApplet()
{
alwaysRender = true; // render everytime the screen is updated
alwaysRender = true; // render every time the screen is updated
// Show at boot, if user has previously enabled the feature
if (settings->optionalFeatures.batteryIcon)
@@ -91,4 +91,4 @@ void InkHUD::BatteryIconApplet::onRender(bool full)
drawRect(sliceL, sliceT, sliceW, sliceH, BLACK);
}
#endif
#endif
@@ -2,7 +2,7 @@
/*
System Applet to render an on-screeen keyboard
System Applet to render an on-screen keyboard
*/
@@ -45,7 +45,7 @@ void InkHUD::LogoApplet::onRender(bool full)
int16_t logoCY = Y(0.5 - 0.05);
// Invert colors if black-on-white
// Used during shutdown, to resport display health
// Used during shutdown, to report display health
// Todo: handle this in InkHUD::Renderer instead
if (inverted) {
fillScreen(BLACK);
@@ -186,4 +186,4 @@ int32_t InkHUD::LogoApplet::runOnce()
return OSThread::disable();
}
#endif
#endif
@@ -2028,7 +2028,7 @@ void InkHUD::MenuApplet::sendText(NodeNum dest, ChannelIndex channel, const char
service->sendToMesh(p, RX_SRC_LOCAL, true); // Send to mesh, cc to phone
}
// Free up any heap mmemory we'd used while selecting / sending canned messages
// Free up any heap memory we'd used while selecting / sending canned messages
void InkHUD::MenuApplet::freeCannedMessageResources()
{
cm.selectedMessageItem = nullptr;
@@ -73,7 +73,7 @@ ProcessMessage InkHUD::FavoritesMapApplet::handleReceived(const meshtastic_MeshP
}
} else {
// For non-local packets, this applet only reacts to favorited nodes.
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
if (!sender || !sender->is_favorite)
return ProcessMessage::CONTINUE;
@@ -7,7 +7,7 @@ Displays a thread-view of incoming and outgoing message for a specific channel
The channel for this applet is set in the constructor,
when the applet is added to WindowManager in the setupNicheGraphics method.
Several messages are saved to flash at shutdown, to preseve applet between reboots.
Several messages are saved to flash at shutdown, to preserve applet between reboots.
This class has its own internal method for saving and loading to fs, which interacts directly with the FSCommon layer.
If the amount of flash usage is unacceptable, we could keep these in RAM only.
@@ -55,4 +55,4 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule
} // namespace NicheGraphics::InkHUD
#endif
#endif
+2 -2
View File
@@ -53,7 +53,7 @@ class Renderer : protected concurrency::OSThread
uint16_t height();
private:
// Make attemps to render / update, once triggered by requestUpdate or forceUpdate
// Make attempts to render / update, once triggered by requestUpdate or forceUpdate
int32_t runOnce() override;
// Apply the display rotation to handled pixels
@@ -95,4 +95,4 @@ class Renderer : protected concurrency::OSThread
} // namespace NicheGraphics::InkHUD
#endif
#endif
+2 -2
View File
@@ -649,7 +649,7 @@ void InkHUD::WindowManager::refocusTile()
}
}
// Seach for any applets which believe they are foreground, but no longer have a valid tile
// Search for any applets which believe they are foreground, but no longer have a valid tile
// Tidies up after layout changes at runtime
void InkHUD::WindowManager::findOrphanApplets()
{
@@ -679,4 +679,4 @@ void InkHUD::WindowManager::findOrphanApplets()
}
}
#endif
#endif
+1 -1
View File
@@ -733,7 +733,7 @@ To add support for additional encodings, add to the `AppletFont::Encodings` enum
#### Custom Line Height
Some fonts may have a handful of especially tall characters, especially extended-ASCII fonts with diacritcs. Ideally, the font should be modified to help resolve this, but if the problem remains, manual offsets to the automatically determined line height can be specified in the constructor.
Some fonts may have a handful of especially tall characters, especially extended-ASCII fonts with diacritics. Ideally, the font should be modified to help resolve this, but if the problem remains, manual offsets to the automatically determined line height can be specified in the constructor.
```cpp
// -2 px of padding above, +1 px of padding below
+2 -2
View File
@@ -59,7 +59,7 @@ void TwoButton::stop()
}
// Attempt to resolve a GPIO pin for the user button, honoring userPrefs.jsonc and device settings
// This helper method isn't used by the TweButton class itself, it could be moved elsewhere.
// This helper method isn't used by the TwoButton class itself, it could be moved elsewhere.
// Intention is to pass this value to TwoButton::setWiring in the setupNicheGraphics method.
uint8_t TwoButton::getUserButtonPin()
{
@@ -308,4 +308,4 @@ int TwoButton::afterLightSleep(esp_sleep_wakeup_cause_t cause)
#endif
#endif
#endif
+5 -4
View File
@@ -271,12 +271,13 @@ int32_t ButtonThread::runOnce()
break;
} // end multipress event
// Do actual shutdown when button released, otherwise the button release
// may wake the board immediatedly.
// Do actual shutdown when button released, otherwise the button release
// may wake the board immediately.
case BUTTON_EVENT_LONG_RELEASED: {
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
// Require press started after boot holdoff to avoid phantom shutdown from floating pins
if (millis() > 30000 && buttonPressStartTime > 30000 && _longLongPress != INPUT_BROKER_NONE &&
(millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) {
evt.inputEvent = _longLongPress;
this->notifyObservers(&evt);
@@ -346,4 +347,4 @@ int ButtonThread::afterLightSleep(esp_sleep_wakeup_cause_t cause)
void ButtonThread::storeClickCount()
{
multipressClickCount = userButton.getNumberClicks();
}
}
+200
View File
@@ -0,0 +1,200 @@
#if defined(M5STACK_CARDPUTER_ADV)
#include "CardputerKeyboard.h"
#include "main.h"
#define _TCA8418_COLS 8
#define _TCA8418_ROWS 7
#define _TCA8418_NUM_KEYS 56
#define _TCA8418_MULTI_TAP_THRESHOLD 1500
using Key = TCA8418KeyboardBase::TCA8418Key;
constexpr uint8_t modifierFnKey = 2;
constexpr uint8_t modifierFn = 0b0010;
constexpr uint8_t modifierCtrlKey = 3;
constexpr uint8_t modifierShiftKey = 6;
constexpr uint8_t modifierShift = 0b0001;
constexpr uint8_t modifierOptKey = 7;
constexpr uint8_t modifierAltKey = 11;
// Num chars per key, Modulus for rotating through characters
static uint8_t CardputerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
static unsigned char CardputerTapMap[_TCA8418_NUM_KEYS][3] = {{'`', '~', Key::ESC},
{Key::TAB, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{'1', '!', 0x00},
{'q', 'Q', Key::REBOOT},
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{'2', '@', 0x00},
{'w', 'W', 0x00},
{'a', 'A', 0x00},
{0x00, 0x00, 0x00},
{'3', '#', 0x00},
{'e', 'E', 0x00},
{'s', 'S', 0x00},
{'z', 'Z', 0x00},
{'4', '$', 0x00},
{'r', 'R', 0x00},
{'d', 'D', 0x00},
{'x', 'X', 0x00},
{'5', '%', 0x00},
{'t', 'T', 0x00},
{'f', 'F', 0x00},
{'c', 'C', 0x00},
{'6', '^', 0x00},
{'y', 'Y', 0x00},
{'g', 'G', Key::GPS_TOGGLE},
{'v', 'V', 0x00},
{'7', '&', 0x00},
{'u', 'U', 0x00},
{'h', 'H', 0x00},
{'b', 'B', Key::BT_TOGGLE},
{'8', '*', 0x00},
{'i', 'I', 0x00},
{'j', 'J', 0x00},
{'n', 'N', 0x00},
{'9', '(', 0x00},
{'o', 'O', 0x00},
{'k', 'K', 0x00},
{'m', 'M', Key::MUTE_TOGGLE},
{'0', ')', 0x00},
{'p', 'P', Key::SEND_PING},
{'l', 'L', 0x00},
{',', '<', Key::LEFT},
{'_', '-', 0x00},
{'[', '{', 0x00},
{';', ':', Key::UP},
{'.', '>', Key::DOWN},
{'=', '+', 0x00},
{']', '}', 0x00},
{'\'', '"', 0x00},
{'/', '?', Key::RIGHT},
{Key::BSP, 0x00, 0x00},
{'\\', '|', 0x00},
{Key::SELECT, 0x00, 0x00},
{' ', ' ', ' '}};
CardputerKeyboard::CardputerKeyboard()
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
last_tap(0L), char_idx(0), tap_interval(0)
{
reset();
}
void CardputerKeyboard::reset(void)
{
TCA8418KeyboardBase::reset();
}
// handle multi-key presses (shift and alt)
void CardputerKeyboard::trigger()
{
uint8_t count = keyCount();
if (count == 0)
return;
for (uint8_t i = 0; i < count; ++i) {
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
uint8_t key = k & 0x7F;
if (k & 0x80) {
pressed(key);
} else {
released();
state = Idle;
}
}
}
void CardputerKeyboard::pressed(uint8_t key)
{
if (state == Init || state == Busy) {
return;
}
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
modifierFlag = 0;
}
uint8_t next_key = 0;
int row = (key - 1) / 10;
int col = (key - 1) % 10;
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
return; // Invalid key
}
next_key = row * _TCA8418_COLS + col;
state = Held;
uint32_t now = millis();
tap_interval = now - last_tap;
updateModifierFlag(next_key);
if (isModifierKey(next_key)) {
last_modifier_time = now;
}
if (tap_interval < 0) {
last_tap = 0;
state = Busy;
return;
}
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
char_idx = 0;
} else {
char_idx += 1;
}
last_key = next_key;
last_tap = now;
}
void CardputerKeyboard::released()
{
if (state != Held) {
return;
}
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
last_key = -1;
state = Idle;
return;
}
uint32_t now = millis();
last_tap = now;
if (inputBroker->menuMode && modifierFlag == 0) {
if (last_key == 0 || last_key == 43 || last_key == 46 || last_key == 47 ||
last_key == 51) { // esc, left, up, down, right key
modifierFlag = modifierFn;
}
}
queueEvent(CardputerTapMap[last_key][modifierFlag % CardputerTapMod[last_key]]);
if (isModifierKey(last_key) == false)
modifierFlag = 0;
}
void CardputerKeyboard::updateModifierFlag(uint8_t key)
{
if (key == modifierShiftKey) {
modifierFlag ^= modifierShift;
} else if (key == modifierFnKey) {
modifierFlag ^= modifierFn;
}
}
bool CardputerKeyboard::isModifierKey(uint8_t key)
{
return (key == modifierShiftKey || key == modifierFnKey);
}
#endif
+26
View File
@@ -0,0 +1,26 @@
#include "TCA8418KeyboardBase.h"
class CardputerKeyboard : public TCA8418KeyboardBase
{
public:
CardputerKeyboard();
void reset(void);
void trigger(void) override;
virtual ~CardputerKeyboard() {}
protected:
void pressed(uint8_t key) override;
void released(void) override;
void updateModifierFlag(uint8_t key);
bool isModifierKey(uint8_t key);
private:
uint8_t modifierFlag;
uint32_t last_modifier_time;
int8_t last_key;
int8_t next_key;
uint32_t last_tap;
uint8_t char_idx;
int32_t tap_interval;
};
+16 -1
View File
@@ -100,13 +100,28 @@ void InputBroker::processInputEventQueue()
int InputBroker::handleInputEvent(const InputEvent *event)
{
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
#if HAS_SCREEN
bool screenWasOff = false;
if (screen) {
screenWasOff = !screen->isScreenOn();
}
#endif
powerFSM.trigger(EVENT_INPUT);
if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule &&
moduleConfig.external_notification.enabled && externalNotificationModule->nagging()) {
externalNotificationModule->stopNow();
// If this turns off a notification, don't further process the event
return 0;
}
#if HAS_SCREEN
if (screen && screenWasOff) {
// If the screen was off, it is in the process of turning on, and we just drop the event
return 0;
}
#endif
this->notifyObservers(event);
return 0;
}
+1
View File
@@ -70,6 +70,7 @@ class InputBroker : public Observable<const InputEvent *>
public:
InputBroker();
bool menuMode = true;
void registerSource(Observable<const InputEvent *> *source);
void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
+2 -2
View File
@@ -177,7 +177,7 @@ void MPR121Keyboard::reset()
delay(20);
writeRegister(_MPR121_REG_CONFIG2, 0x21);
delay(20);
// Enter run mode by Seting partial filter calibration tracking, disable proximity detection, enable 12 channels
// Enter run mode by setting partial filter calibration tracking, disable proximity detection, enable 12 channels
writeRegister(_MPR121_REG_ELECTRODE_CONFIG,
ECR_CALIBRATION_TRACK_FROM_FULL_FILTER | ECR_PROXIMITY_DETECTION_OFF | ECR_TOUCH_DETECTION_12CH);
delay(100);
@@ -430,4 +430,4 @@ void MPR121Keyboard::writeRegister(uint8_t reg, uint8_t value)
if (writeCallback) {
writeCallback(m_addr, data[0], &(data[1]), 1);
}
}
}
+2 -2
View File
@@ -59,7 +59,7 @@ int32_t SeesawRotary::runOnce()
wasPressed = currentlyPressed;
int32_t new_position = ss.getEncoderPosition();
// did we move arounde?
// did we move around?
if (encoder_position != new_position) {
if (encoder_position == 0 && new_position != 1) {
e.inputEvent = INPUT_BROKER_ALT_PRESS;
@@ -80,4 +80,4 @@ int32_t SeesawRotary::runOnce()
return 50;
}
#endif
#endif
+1 -1
View File
@@ -88,7 +88,7 @@ void TCA8418Keyboard::pressed(uint8_t key)
// Check if the key is the same as the last one or if the time interval has passed
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
char_idx = 0; // Reset char index if new key or long press
should_backspace = false; // dont backspace on new key
should_backspace = false; // don't backspace on new key
} else {
char_idx += 1; // Cycle through characters if same key pressed
should_backspace = true; // allow backspace on same key
+2 -2
View File
@@ -43,7 +43,7 @@ int32_t TouchScreenBase::runOnce()
// process touch events
int16_t x, y;
bool touched = getTouch(x, y);
if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turing off the screen
if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turning off the screen
touched = false;
if (touched) {
this->setInterval(20);
@@ -123,7 +123,7 @@ int32_t TouchScreenBase::runOnce()
}
}
#else
// fire TAP event when no 2nd tap occured within time
// fire TAP event when no 2nd tap occurred within time
if (_tapped) {
_tapped = false;
e.touchEvent = static_cast<char>(TOUCH_ACTION_TAP);
+5 -1
View File
@@ -7,6 +7,8 @@
#include "TDeckProKeyboard.h"
#elif defined(T_LORA_PAGER)
#include "TLoraPagerKeyboard.h"
#elif defined(M5STACK_CARDPUTER_ADV)
#include "CardputerKeyboard.h"
#elif defined(HACKADAY_COMMUNICATOR)
#include "HackadayCommunicatorKeyboard.h"
#else
@@ -22,6 +24,8 @@ KbI2cBase::KbI2cBase(const char *name)
TCAKeyboard(*(new TDeckProKeyboard()))
#elif defined(T_LORA_PAGER)
TCAKeyboard(*(new TLoraPagerKeyboard()))
#elif defined(M5STACK_CARDPUTER_ADV)
TCAKeyboard(*(new CardputerKeyboard()))
#elif defined(HACKADAY_COMMUNICATOR)
TCAKeyboard(*(new HackadayCommunicatorKeyboard()))
#else
@@ -487,7 +491,7 @@ int32_t KbI2cBase::runOnce()
e.kbchar = 0;
break;
case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker))
// toggle moddifiers button.
// toggle modifiers button.
is_sym = !is_sym;
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that the
+33 -3
View File
@@ -9,6 +9,7 @@
#include "PowerMon.h"
#include "RadioLibInterface.h"
#include "ReliableRouter.h"
#include "TransmitHistory.h"
#include "airtime.h"
#include "buzz.h"
#include "power/PowerHAL.h"
@@ -146,6 +147,15 @@ extern void tftSetup(void);
UdpMulticastHandler *udpHandler = nullptr;
#endif
#if HAS_BLE_MESH
BLEMeshHandler *bleMeshHandler = nullptr;
#if defined(ARCH_NRF52)
#include "platform/nrf52/NRF52BLEMesh.h"
#elif defined(ARCH_ESP32)
#include "platform/esp32/ESP32BLEMesh.h"
#endif
#endif
#if defined(TCXO_OPTIONAL)
float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if TCXO is optional, put this here so it can be changed further down.
#endif
@@ -246,7 +256,6 @@ uint32_t timeLastPowered = 0;
static OSThread *powerFSMthread;
OSThread *ambientLightingThread;
RadioInterface *rIf = NULL;
RadioLibHal *RadioLibHAL = NULL;
/**
@@ -389,8 +398,8 @@ void setup()
#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
#ifndef SENSECAP_INDICATOR
// use PSRAM for malloc calls > 256 bytes
heap_caps_malloc_extmem_enable(256);
// use PSRAM for malloc calls > 2048 bytes
heap_caps_malloc_extmem_enable(2048);
#endif
#endif
@@ -704,6 +713,9 @@ void setup()
// We do this as early as possible because this loads preferences from flash
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
nodeDB = new NodeDB;
// Initialize transmit history to persist broadcast throttle timers across reboots
TransmitHistory::getInstance()->loadFromDisk();
#if HAS_TFT
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
tftSetup();
@@ -881,6 +893,17 @@ void setup()
udpHandler->start();
}
#endif
#endif
#if HAS_BLE_MESH
LOG_DEBUG("Start BLE mesh handler");
#if defined(ARCH_NRF52)
bleMeshHandler = new NRF52BLEMesh();
#elif defined(ARCH_ESP32)
bleMeshHandler = new ESP32BLEMesh();
#endif
if (bleMeshHandler)
bleMeshHandler->start();
#endif
service = new MeshService();
service->init();
@@ -1125,6 +1148,13 @@ void loop()
lastRadioMissedIrqPoll = millis();
RadioLibInterface::instance->pollMissedIrqs();
}
// Periodic AGC reset — warm sleep + recalibrate to prevent stuck AGC gain
static uint32_t lastAgcReset;
if (!Throttle::isWithinTimespanMs(lastAgcReset, AGC_RESET_INTERVAL_MS)) {
lastAgcReset = millis();
RadioLibInterface::instance->resetAGC();
}
}
#ifdef DEBUG_STACK
+5
View File
@@ -62,6 +62,11 @@ extern AudioThread *audioThread;
extern UdpMulticastHandler *udpHandler;
#endif
#if HAS_BLE_MESH
#include "mesh/BLEMeshHandler.h"
extern BLEMeshHandler *bleMeshHandler;
#endif
// Global Screen singleton.
extern graphics::Screen *screen;
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#if HAS_BLE_MESH
#include "MeshTypes.h"
#include "Router.h"
#include "mesh-pb-constants.h"
// Meshtastic BLE mesh manufacturer data identifier
// Using 0xFFFF (reserved for testing) until a real BLE SIG company ID is assigned
#define BLE_MESH_COMPANY_ID 0xFFFF
#define BLE_MESH_PROTOCOL_VERSION 1
// TODO: Add TRANSPORT_BLE_MESH = 8 to mesh.proto TransportMechanism enum and regenerate.
// Until then, define it here so the code compiles.
#ifndef meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_MESH
#define meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_MESH ((meshtastic_MeshPacket_TransportMechanism)8)
#endif
class BLEMeshHandler
{
public:
BLEMeshHandler() : isRunning(false) {}
virtual ~BLEMeshHandler() {}
virtual void start() = 0;
virtual void stop() = 0;
virtual void onBluetoothReady() {}
// Called from Router::send() to broadcast packet over BLE mesh
virtual bool onSend(const meshtastic_MeshPacket *mp) = 0;
protected:
bool isRunning;
// Decode received BLE mesh data and enqueue into router
void deliverToRouter(const uint8_t *data, size_t len)
{
meshtastic_MeshPacket mp = meshtastic_MeshPacket_init_zero;
bool decoded = pb_decode_from_bytes(data, len, &meshtastic_MeshPacket_msg, &mp);
if (!decoded || mp.which_payload_variant != meshtastic_MeshPacket_encrypted_tag)
return;
mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_MESH;
mp.pki_encrypted = false;
mp.public_key.size = 0;
memset(mp.public_key.bytes, 0, sizeof(mp.public_key.bytes));
mp.rx_snr = 0;
mp.rx_rssi = 0;
UniquePacketPoolPacket p = packetPool.allocUniqueCopy(mp);
if (p && router)
router->enqueueReceivedMessage(p.release());
}
// Encode a mesh packet for BLE transmission, returns encoded length
size_t encodeForBLE(const meshtastic_MeshPacket *mp, uint8_t *buf, size_t bufLen)
{
return pb_encode_to_bytes(buf, bufLen, &meshtastic_MeshPacket_msg, mp);
}
};
#endif // HAS_BLE_MESH
+3 -7
View File
@@ -22,6 +22,8 @@ const char *Channels::serialChannel = "serial";
const char *Channels::mqttChannel = "mqtt";
#endif
meshtastic_Channel dummyChannel = {.index = -1};
uint8_t xorHash(const uint8_t *p, size_t len)
{
uint8_t code = 0;
@@ -309,13 +311,7 @@ meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex)
return *ch;
} else {
LOG_ERROR("Invalid channel index %d > %d, malformed packet received?", chIndex, channelFile.channels_count);
static meshtastic_Channel *ch = (meshtastic_Channel *)malloc(sizeof(meshtastic_Channel));
memset(ch, 0, sizeof(meshtastic_Channel));
// ch.index -1 means we don't know the channel locally and need to look it up by settings.name
// not sure this is handled right everywhere
ch->index = -1;
return *ch;
return dummyChannel;
}
}
+4 -2
View File
@@ -38,11 +38,13 @@ uint32_t Default::getConfiguredOrDefault(uint32_t configured, uint32_t defaultVa
uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes)
{
// If we are a router, we don't scale the value. It's already significantly higher.
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER)
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)
return getConfiguredOrDefaultMs(configured, defaultValue);
// Additionally if we're a tracker or sensor, we want priority to send position and telemetry
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_TRACKER))
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_TRACKER,
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER))
return getConfiguredOrDefaultMs(configured, defaultValue);
return getConfiguredOrDefaultMs(configured, defaultValue) * congestionScalingCoefficient(numOnlineNodes);
+35 -15
View File
@@ -1,5 +1,8 @@
#pragma once
#include <MeshRadio.h>
#include <NodeDB.h>
#include <RadioInterface.h>
#include <cmath>
#include <cstdint>
#include <meshUtils.h>
#define ONE_DAY 24 * 60 * 60
@@ -42,7 +45,10 @@
#define default_mqtt_tls_enabled false
#define IF_ROUTER(routerVal, normalVal) \
((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal))
((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || \
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) \
? (routerVal) \
: (normalVal))
class Default
{
@@ -63,25 +69,39 @@ class Default
if (numOnlineNodes <= 40) {
return 1.0;
} else {
float throttlingFactor = 0.075;
if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW)
throttlingFactor = 0.04;
else if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST)
throttlingFactor = 0.02;
else if (config.lora.use_preset &&
IS_ONE_OF(config.lora.modem_preset, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO,
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW))
throttlingFactor = 0.01;
// Resolve SF and BW from preset or manual config
// When use_preset is true, config.lora.spread_factor and bandwidth may be 0
// because applyModemConfig() sets them on RadioInterface, not on config.lora
float bwKHz;
uint8_t sf;
uint8_t cr;
if (config.lora.use_preset) {
modemPresetToParams(config.lora.modem_preset, false, bwKHz, sf, cr);
} else {
sf = config.lora.spread_factor;
bwKHz = bwCodeToKHz(config.lora.bandwidth);
}
// Guard against invalid values
sf = clampSpreadFactor(sf);
bwKHz = clampBandwidthKHz(bwKHz);
// throttlingFactor = 2^SF / (BW_in_kHz * scaling_divisor)
// With scaling_divisor=100:
// In SF11 and BW=250khz (longfast), this gives 0.08192 rather than the original 0.075
// In SF10 and BW=250khz (mediumslow), this gives 0.04096 rather than the original 0.04
// In SF9 and BW=250khz (mediumfast), this gives 0.02048 rather than the original 0.02
// In SF7 and BW=250khz (shortfast), this gives 0.00512 rather than the original 0.01
float throttlingFactor = static_cast<float>(pow_of_2(sf)) / (bwKHz * 100.0f);
#if USERPREFS_EVENT_MODE
// If we are in event mode, scale down the throttling factor
throttlingFactor = 0.04;
// If we are in event mode, scale down the throttling factor by 4
throttlingFactor = static_cast<float>(pow_of_2(sf)) / (bwKHz * 25.0f);
#endif
// Scaling up traffic based on number of nodes over 40
int nodesOverForty = (numOnlineNodes - 40);
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default)
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by throttle factor
}
}
};
};
+32
View File
@@ -299,6 +299,38 @@ template <typename T> bool LR11x0Interface<T>::isActivelyReceiving()
RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED);
}
#ifdef LR11X0_AGC_RESET
template <typename T> void LR11x0Interface<T>::resetAGC()
{
// Safety: don't reset mid-packet
if (sendingPacket != NULL || (isReceiving && isActivelyReceiving()))
return;
LOG_DEBUG("LR11x0 AGC reset: warm sleep + Calibrate(0x3F)");
// 1. Warm sleep — powers down the analog frontend, resetting AGC state
lora.sleep(true, 0);
// 2. Wake to RC standby for stable calibration
lora.standby(RADIOLIB_LR11X0_STANDBY_RC, true);
// 3. Calibrate all blocks (PLL, ADC, image, RC oscillators)
// calibrate() is protected on LR11x0, so use raw SPI (same as internal implementation)
uint8_t calData = RADIOLIB_LR11X0_CALIBRATE_ALL;
module.SPIwriteStream(RADIOLIB_LR11X0_CMD_CALIBRATE, &calData, 1, true, true);
// 4. Re-calibrate image rejection for actual operating frequency
// Calibrate(0x3F) defaults to 902-928 MHz which is wrong for other regions.
lora.calibrateImageRejection(getFreq() - 4.0f, getFreq() + 4.0f);
// 5. Re-apply RX boosted gain mode
lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
// 6. Resume receiving
startReceive();
}
#endif
template <typename T> bool LR11x0Interface<T>::sleep()
{
// \todo Display actual typename of the adapter, not just `LR11x0`
+4
View File
@@ -27,6 +27,10 @@ template <class T> class LR11x0Interface : public RadioLibInterface
bool isIRQPending() override { return lora.getIrqFlags() != 0; }
#ifdef LR11X0_AGC_RESET
void resetAGC() override;
#endif
protected:
/**
* Specific module instance
+228
View File
@@ -0,0 +1,228 @@
#if HAS_LORA_FEM
#include "LoRaFEMInterface.h"
#if defined(ARCH_ESP32)
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#endif
LoRaFEMInterface loraFEMInterface;
void LoRaFEMInterface::init(void)
{
setLnaCanControl(false); // Default is uncontrollable
#ifdef HELTEC_V4
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
delay(1);
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CSD);
pinMode(LORA_KCT8103L_PA_CSD, INPUT); // detect which FEM is used
delay(1);
if (digitalRead(LORA_KCT8103L_PA_CSD) == HIGH) {
// FEM is KCT8103L
fem_type = KCT8103L_PA;
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CTX);
pinMode(LORA_KCT8103L_PA_CSD, OUTPUT);
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
pinMode(LORA_KCT8103L_PA_CTX, OUTPUT);
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
setLnaCanControl(true);
} else if (digitalRead(LORA_KCT8103L_PA_CSD) == LOW) {
// FEM is GC1109
fem_type = GC1109_PA;
// LORA_GC1109_PA_EN and LORA_KCT8103L_PA_CSD are the same pin and do not need to be repeatedly turned off and held.
// rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN);
pinMode(LORA_GC1109_PA_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_EN, HIGH);
pinMode(LORA_GC1109_PA_TX_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
} else {
fem_type = OTHER_FEM_TYPES;
}
#elif defined(USE_GC1109_PA)
fem_type = GC1109_PA;
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
#if defined(ARCH_ESP32)
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN);
rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_TX_EN);
#endif
delay(1);
pinMode(LORA_GC1109_PA_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_EN, HIGH);
pinMode(LORA_GC1109_PA_TX_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
#elif defined(USE_KCT8103L_PA)
fem_type = KCT8103L_PA;
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
#if defined(ARCH_ESP32)
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CSD);
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CTX);
#endif
delay(1);
pinMode(LORA_KCT8103L_PA_CSD, OUTPUT);
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
pinMode(LORA_KCT8103L_PA_CTX, OUTPUT);
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
setLnaCanControl(true);
#endif
}
void LoRaFEMInterface::setSleepModeEnable(void)
{
#ifdef HELTEC_V4
if (fem_type == GC1109_PA) {
/*
* Do not switch the power on and off frequently.
* After turning off LORA_GC1109_PA_EN, the power consumption has dropped to the uA level.
*/
digitalWrite(LORA_GC1109_PA_EN, LOW);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
} else if (fem_type == KCT8103L_PA) {
// shutdown the PA
digitalWrite(LORA_KCT8103L_PA_CSD, LOW);
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, LOW);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
#elif defined(USE_KCT8103L_PA)
// shutdown the PA
digitalWrite(LORA_KCT8103L_PA_CSD, LOW);
#endif
}
void LoRaFEMInterface::setTxModeEnable(void)
{
#ifdef HELTEC_V4
if (fem_type == GC1109_PA) {
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
} else if (fem_type == KCT8103L_PA) {
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
#elif defined(USE_KCT8103L_PA)
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
#endif
}
void LoRaFEMInterface::setRxModeEnable(void)
{
#ifdef HELTEC_V4
if (fem_type == GC1109_PA) {
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
} else if (fem_type == KCT8103L_PA) {
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
if (lna_enabled) {
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
} else {
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
#elif defined(USE_KCT8103L_PA)
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
if (lna_enabled) {
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
} else {
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
#endif
}
void LoRaFEMInterface::setRxModeEnableWhenMCUSleep(void)
{
#ifdef HELTEC_V4
// Keep GC1109 FEM powered during deep sleep so LNA remains active for RX wake.
// Set PA_POWER and PA_EN HIGH (overrides SX126xInterface::sleep() shutdown),
// then latch with RTC hold so the state survives deep sleep.
digitalWrite(LORA_PA_POWER, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER);
if (fem_type == GC1109_PA) {
digitalWrite(LORA_GC1109_PA_EN, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN);
gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN);
} else if (fem_type == KCT8103L_PA) {
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CSD);
if (lna_enabled) {
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
} else {
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CTX);
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_PA_POWER, HIGH);
digitalWrite(LORA_GC1109_PA_EN, HIGH);
#if defined(ARCH_ESP32)
rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER);
rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN);
gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN);
#endif
#elif defined(USE_KCT8103L_PA)
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CSD);
if (lna_enabled) {
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
} else {
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CTX);
#endif
}
void LoRaFEMInterface::setLNAEnable(bool enabled)
{
lna_enabled = enabled;
}
int8_t LoRaFEMInterface::powerConversion(int8_t loraOutputPower)
{
#ifdef HELTEC_V4
const uint16_t gc1109_tx_gain[] = {11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7};
const uint16_t kct8103l_tx_gain[] = {13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7};
const uint16_t *tx_gain;
uint16_t tx_gain_num;
if (fem_type == GC1109_PA) {
tx_gain = gc1109_tx_gain;
tx_gain_num = sizeof(gc1109_tx_gain) / sizeof(gc1109_tx_gain[0]);
} else if (fem_type == KCT8103L_PA) {
tx_gain = kct8103l_tx_gain;
tx_gain_num = sizeof(kct8103l_tx_gain) / sizeof(kct8103l_tx_gain[0]);
} else {
return loraOutputPower;
}
#else
#ifdef ARCH_PORTDUINO
const uint16_t *tx_gain = portduino_config.tx_gain_lora;
uint16_t tx_gain_num = portduino_config.num_pa_points;
#else
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
uint16_t tx_gain_num = NUM_PA_POINTS;
#endif
#endif
for (int radio_dbm = 0; radio_dbm < tx_gain_num; radio_dbm++) {
if (((radio_dbm + tx_gain[radio_dbm]) > loraOutputPower) ||
((radio_dbm == (tx_gain_num - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= loraOutputPower))) {
// we've exceeded the power limit, or hit the max we can do
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", loraOutputPower, tx_gain[radio_dbm]);
loraOutputPower -= tx_gain[radio_dbm];
break;
}
}
return loraOutputPower;
}
#endif
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#if HAS_LORA_FEM
#include "configuration.h"
#include <stdint.h>
typedef enum { GC1109_PA, KCT8103L_PA, OTHER_FEM_TYPES } LoRaFEMType;
class LoRaFEMInterface
{
public:
LoRaFEMInterface() {}
virtual ~LoRaFEMInterface() {}
void init(void);
void setSleepModeEnable(void);
void setTxModeEnable(void);
void setRxModeEnable(void);
void setRxModeEnableWhenMCUSleep(void);
void setLNAEnable(bool enabled);
int8_t powerConversion(int8_t loraOutputPower);
bool isLnaCanControl(void) { return lna_can_control; }
void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
private:
LoRaFEMType fem_type;
bool lna_enabled = false;
bool lna_can_control = false;
};
extern LoRaFEMInterface loraFEMInterface;
#endif
+12
View File
@@ -106,6 +106,18 @@ class MeshModule
/* We allow modules to ignore a request without sending an error if they have a specific reason for it. */
bool ignoreRequest = false;
/**
* Check if the current request is a multi-hop broadcast. Modules should call this in allocReply()
* and return NULL to prevent reply storms from broadcast requests that have already been relayed.
*/
bool isMultiHopBroadcastRequest()
{
if (currentRequest && isBroadcast(currentRequest->to) && currentRequest->hop_limit < currentRequest->hop_start) {
return true;
}
return false;
}
/** If a bound channel name is set, we will only accept received packets that come in on that channel.
* A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface
* are allowed on any channel (this lets the local user do anything).
+23
View File
@@ -184,6 +184,29 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p)
}
}
if (backPacket->tx_after) {
// Check if there's a late packet at the queue end
auto now = millis();
if (backPacket->tx_after < now && (!p->tx_after || backPacket->tx_after > p->tx_after)) {
int32_t dt = (int32_t)(backPacket->tx_after - now);
if (p->tx_after) {
LOG_WARN("Dropping late packet 0x%08x with TX delay %dms to make room in the TX queue for packet 0x%08x with "
"TX delay %ums",
backPacket->id, dt, p->id, p->tx_after - now);
} else {
LOG_WARN("Dropping late packet 0x%08x with TX delay %dms to make room in the TX queue for packet 0x%08x "
"with no TX delay",
backPacket->id, dt, p->id);
}
queue.pop_back();
packetPool.release(backPacket);
// Insert the new packet in the correct order
enqueue(p);
return true;
}
}
// If the back packet's priority is not lower, no replacement occurs
return false;
}

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