Compare commits

...

170 Commits

Author SHA1 Message Date
Jonathan Bennett
5633848d75 Tryfix nohop 2026-01-12 23:17:14 -06:00
Jonathan Bennett
8b8c1881a8 Add NoHopPorts 2026-01-11 17:33:23 -06:00
Jonathan Bennett
a1d6978626 Add portnum whitelist for testing 2026-01-10 23:27:44 -06:00
Jonathan Bennett
a67cf0f726 Add SF++ metrics logging 2026-01-10 13:34:25 -06:00
Jonathan Bennett
456fa3ddeb Merge branch 'develop' into sfpp 2026-01-09 16:30:32 -06:00
Jonathan Bennett
5adc9663b7 Handle text messages from local node 2026-01-09 16:29:54 -06:00
Ben Meadors
b6512d3de1 Merge pull request #9237 from meshtastic/master
Master to dev merge
2026-01-09 14:56:54 -06:00
Manuel
214c76ce1b T-Watch S3 Plus GPS support (#9235)
* Upgrade trunk (#9229)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>

* support T-Watch S3 Plus GPS

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-09 11:48:27 -06:00
Jonathan Bennett
b002844aa0 Add Rak 6421 autoconf (#9010)
* Add Rak 6421 autoconf

* Minor memory safety hardening
2026-01-09 11:36:53 -06:00
Jason P
925381ef7b Fix TFT_MESH settings across setting and recalling (#9234)
* Fix TFT_MESH settings across setting and recalling

* Fix a word in documentation

* Update src/graphics/Screen.cpp

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

* Update src/graphics/Screen.cpp

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-09 11:30:49 -05:00
Jonathan Bennett
b12acba44f CH341 MAC address derivation from serial and product string (#9226)
Updated MAC address derivation logic to include product string in hashing.
2026-01-09 05:42:00 -06:00
github-actions[bot]
6b8e5e9d7b Upgrade trunk (#9229)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-09 05:41:19 -06:00
Jonathan Bennett
5de0654819 Logging 2026-01-08 22:38:55 -06:00
Jonathan Bennett
489de09375 Use correct name for ALT_BUTTON_PIN (#9225) 2026-01-08 19:19:17 -06:00
Jason P
390f0c8248 Screenless Devices want to mute too! (#9210)
* Screenless Devices want to mute too!
* Add logging for actions
* Gate to screenless devices only
* WisMesh Tag was missing HAS_SCREEN 0

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-08 16:44:05 -06:00
Jonathan Bennett
ab781e9f2d Don't try to send scratch messages from stratum0 2026-01-08 15:28:33 -06:00
Jonathan Bennett
595b5f19b3 Fix message hash recalc 2026-01-08 14:48:21 -06:00
Jonathan Bennett
4bb93c1ed2 Don't double add to canon scratch 2026-01-08 14:35:55 -06:00
Jonathan Bennett
5582e94009 Minor fix 2026-01-08 14:30:31 -06:00
Jonathan Bennett
e33fbca8d6 Refactor and bugfix 2026-01-08 14:07:42 -06:00
Jonathan Bennett
aca7fe9f95 Properly empty canon_scratch 2026-01-08 13:32:11 -06:00
Jonathan Bennett
5a0644cd4f Code refactor 2026-01-08 13:02:14 -06:00
Jonathan Bennett
e990198628 Merge branch 'develop' into sfpp 2026-01-08 12:07:24 -06:00
Jonathan Bennett
792e930e45 Trunk 2026-01-08 12:07:14 -06:00
Jonathan Bennett
6c69d9e74c Disable SF++ by default in forced sim mode (When running with -s) 2026-01-08 11:57:25 -06:00
Jonathan Bennett
e03f1b5c5e Merge branch 'develop' into sfpp 2026-01-08 11:42:50 -06:00
Ben Meadors
5fab45c133 Merge branch 'master' into develop 2026-01-08 08:50:21 -06:00
Ben Meadors
c6e070461a Remove disabled gh action 2026-01-08 08:44:05 -06:00
Ben Meadors
f289b78061 Fix rotary regression and tighten up playBeep (#9221)
* Fix T-LoRA rotary regression and tighten up playBeep

* Derp

* Increase duration of chirp sound in playChirp
2026-01-08 08:17:45 -06:00
Heath Dutton🕴️
b4369b2730 Fix screen not sleeping due to power status updates (#9216)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-08 08:04:17 -06:00
github-actions[bot]
29d0d5e559 Upgrade trunk (#9219)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-08 05:42:13 -06:00
Wessel
6e110788fd 🔧 Fix LNA/PA power control for Heltec v4, wireless tracker v2 (#9029)
* Fix LNA/PA power control for Heltec v4, wireless tracker v2

* Stop using pin 46 as RF switch, just let DIO2 switch handle the RF path

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-08 05:42:01 -06:00
Jonathan Bennett
f46a9dfe7b misc 2026-01-08 01:07:18 -06:00
Jonathan Bennett
9824357c50 Stratum0 fix 2026-01-08 00:46:00 -06:00
Jonathan Bennett
942f2cb3d1 Tryfix next 2026-01-08 00:32:09 -06:00
Jonathan Bennett
76beeda392 Re-add exception for empty chain 2026-01-08 00:12:49 -06:00
Jonathan Bennett
821735495a Unbork the things 2026-01-08 00:08:49 -06:00
Heath Dutton🕴️
86326f294d Fix TSL2591 detection by adding command bit to register read (#9215) 2026-01-08 17:08:41 +11:00
Jonathan Bennett
9ab2ee3483 Check for valid hash during commit 2026-01-07 23:42:38 -06:00
Jonathan Bennett
fb7af18f4f Add needed support bits for the Meshstick (#9042)
* Add needed support bits for the Meshstick

* Portduino: Reduce allowed length by one byte to prevent possible overflow
2026-01-07 23:04:02 -06:00
Jonathan Bennett
4e92f7fa09 Merge branch 'develop' into sfpp 2026-01-07 23:00:16 -06:00
Jonathan Bennett
ae2a06eccd SFPP Logging misc 2026-01-07 17:27:14 -06:00
Jonathan Bennett
4d303c95d1 Add list of text message packet IDs, and check for dupes (#9180)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-07 16:17:38 -06:00
Ben Meadors
1a6cbb5caa Migrate all of the Meshtastic API attributes into the ini as a source of truth (#9214)
* Migrate all of the Meshtastic API attributes into the ini as a source of truth

* Cleanup garbage coalescing

* Another spot

* We already account for inkhud and mui

* Consolidate

* Removed them

* Boogers

* Infer

* Copying manifest should always succeed

* Remove portduino guards

* Rename

* None
2026-01-07 15:25:38 -06:00
Jonathan Bennett
3ae331eb89 Add canon_scratch to SF++ 2026-01-07 12:44:22 -06:00
github-actions[bot]
a6c4683ddc Upgrade trunk (#9208)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-07 05:50:17 -06:00
Jason P
70f909d718 Fix Function + M in code (#9200) 2026-01-06 16:02:41 -06:00
Jason P
9c1d55c844 Add option to Mute/Unmute Channel to BaseUI (#9194) 2026-01-06 15:26:45 -06:00
Bob Reese
ba9d0e6fa3 RadioInterface::getRetransmissionMsec now handles encrypted packets correctly (#9184) 2026-01-06 12:34:48 -06:00
Lewis He
da11cc739d Added support for the new SSD1306 control panel. (#9192) 2026-01-06 07:25:31 -06:00
santosvivos
9f5170a0bc Add LilyGO T-Beam 1W support (#8967)
* Add LilyGO T-Beam 1W support
- Add board definition and variant files for ESP32-S3 based T-Beam 1W
- Add RF95_FAN_EN support to SX126xInterface for PA cooling fan
- Add SX126X_PA_RAMP_US for configurable PA ramp time (800us for 1W PA)
- Configure RF switch: DIO2 for PA, GPIO 21 for LNA control

* Set TX_GAIN_LORA to 10dB per PR feedback (offset for 1W PA)

* Apply suggestion from @Copilot

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

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-06 06:23:28 -06:00
github-actions[bot]
594f27c3ff Upgrade trunk (#9183)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-06 05:27:14 -06:00
Ben Meadors
e648e26c17 Merge pull request #9191 from meshtastic/bme-native
BME680 on Native
2026-01-05 20:55:44 -06:00
Jonathan Bennett
1669a027e6 BME680 on Native
Co-authored-by: juanjin-dev <juanjin.dev@gmail.com>
2026-01-05 19:33:41 -06:00
Ben Meadors
105d657359 Merge pull request #9189 from vidplace7/actions-feature-branches 2026-01-05 16:52:57 -06:00
Austin Lane
37ab800500 Actions: CI for feature/ branches
...and pioarduino
2026-01-05 17:44:07 -05:00
Jonathan Bennett
74a6c9f447 Alpine docker fix 2026-01-04 23:16:22 -06:00
Jonathan Bennett
c77709a327 Fix the other links_behind underflow 2026-01-04 23:03:47 -06:00
Jonathan Bennett
325f7d2e55 Don't process packet when decoding fails 2026-01-04 21:11:47 -06:00
Jonathan Bennett
c6fc7986f1 More sanity checks on incoming messages 2026-01-04 21:06:36 -06:00
Jonathan Bennett
8ecce1eb5c Fix for integer overflow 2026-01-04 20:56:01 -06:00
Jonathan Bennett
21c0dcaabb Merge branch 'develop' into sfpp 2026-01-04 16:55:59 -06:00
Sergey Galkin
0c553c40d4 Fix zero in sp02 and Heart Rate on screen (#9174)
Fixed zero in sp02 and Heart Rate in HealthTelemetry screen
2026-01-05 07:57:49 +11:00
Jonathan Bennett
1b13f872db Don't double-process rebroadcast messages 2026-01-04 14:24:51 -06:00
Jonathan Bennett
8b5141ddb7 Merge branch 'develop' into sfpp 2026-01-04 13:26:41 -06:00
Iris
17b075a11c added tcxo definition to mesh-tab (#8604) 2026-01-04 05:57:50 -06:00
renovate[bot]
15f5b35859 chore(deps): update meshtastic/device-ui digest to 272defc (#9166)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-04 05:22:50 -06:00
Valentyn Diduryk
25bdefecb2 Fixed shouldFilterReceived function to check prev relay accoding to the function definition (#9168) 2026-01-04 05:22:26 -06:00
Jorropo
beb268ff25 Revert "add a .clang-format file (#9154)" (#9172)
I thought git would be smart enough to understand all the whitespace changes but even with all the flags I know to make it ignore theses it still blows up if there are identical changes on both sides.

I have a solution but it require creating a new commit at the merge base for each conflicting PR and merging it into develop.

I don't think blowing up all PRs is worth for now, maybe if we can coordinate this for V3 let's say.

This reverts commit 0d11331d18.
2026-01-04 05:15:53 -06:00
Jonathan Bennett
ee25a0a0e1 Actually include the counter in CANON ANNOUNCE 2026-01-03 21:40:38 -06:00
Jonathan Bennett
436f174bce Actually include the chain counter in CANON ANNOUNCE 2026-01-03 21:11:44 -06:00
Jonathan Bennett
a34cd4ca6f Better log messages 2026-01-03 21:11:22 -06:00
Jonathan Bennett
8c37669213 Check links behind when receiving a link 2026-01-03 20:59:41 -06:00
Jonathan Bennett
8a059bae23 Add option to clear a chain when it falls too far behind 2026-01-03 20:36:37 -06:00
Jorropo
0d11331d18 add a .clang-format file (#9154) 2026-01-03 14:19:24 -06:00
Jonathan Bennett
1869f2108d Add sqlite dev to more dockerfiles 2026-01-03 12:41:41 -06:00
Jonathan Bennett
f5b41c2f2c Add missed comma 2026-01-03 12:38:59 -06:00
Jonathan Bennett
83c8875060 Merge branch 'develop' into sfpp 2026-01-03 12:34:44 -06:00
Jonathan Bennett
9134239faa Add sqlite to build requires 2026-01-03 12:34:10 -06:00
renovate[bot]
21ca25404a chore(deps): update dorny/test-reporter action to v2.5.0 (#9167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-03 12:00:38 -06:00
Tom Fifield
abab6ce815 Fix link formatting in welcome message (#9163) 2026-01-03 06:00:23 -06:00
brad112358
52907e4c44 Faster rotary encoder events (#9146)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-01-02 20:22:40 -06:00
Ben Meadors
3a90781e1b Add support for LilyGo T-Echo Plus (#9149)
* Add t-echo plus support

* T-Echo plus hw model

* Cruft

* Fix

* Added InkHUD style touch backlight control
2026-01-02 20:14:09 -06:00
Jonathan Bennett
f63dadd19e Add custom coding rate configuration for LoRa (#9155) 2026-01-02 16:23:01 -06:00
Jonathan Bennett
b3d1d563e9 Merge branch 'develop' into sfpp 2026-01-02 16:17:28 -06:00
Jonathan Bennett
c7f816e63f Add StoreAndForward 2026-01-02 16:13:33 -06:00
Ben Meadors
9313d465f6 I think this is supposed to be extra 2026-01-02 15:58:54 -06:00
Jason P
004746683e Refactored some of the system menus to the new DRY method (Redux) (#9152)
* Refactored some of the system menus to the new DRY method

* Fix menu name from Position to GPS
2026-01-02 15:34:25 -06:00
Jonathan Bennett
dd4fb6b0bc Add chain speculation from scratch 2026-01-02 14:43:18 -06:00
Jonathan Bennett
87798429fa Log rebroadcast timeouts 2026-01-02 11:48:42 -06:00
github-actions[bot]
1e914140ca Update protobufs (#9148)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-02 08:35:52 -06:00
github-actions[bot]
caceaf424a Automated version bumps (#9030)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-02 06:56:02 -06:00
Ben Meadors
75144d2028 Update security policy to reflect new stage 2026-01-02 06:42:28 -06:00
Jonathan Bennett
78baaf4484 Check for existing commit hash, etc 2026-01-01 23:25:49 -06:00
Jonathan Bennett
b3b115b6a6 Don't process own packets in SF++ 2026-01-01 22:40:53 -06:00
Jonathan Bennett
b90b5ff40e Merge branch 'develop' into sfpp 2026-01-01 22:17:04 -06:00
Jonathan Bennett
b7028fff08 Add peers table 2026-01-01 22:16:35 -06:00
Jonathan Bennett
7d6a0f20c6 Add extra check for end of chain matching 2026-01-01 22:16:23 -06:00
github-actions[bot]
b5e952b008 Upgrade trunk (#9128)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-01-01 19:17:55 -06:00
Ben Meadors
27b522b55a Merge branch 'master' into develop 2026-01-01 18:25:18 -06:00
renovate[bot]
11b5f1a4fe chore(deps): update dorny/test-reporter action to v2.4.0 (#9135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-01 18:04:13 -06:00
renovate[bot]
f9c9350f45 chore(deps): update meshtastic/device-ui digest to a8e2f94 (#9140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-01 18:03:27 -06:00
Jonathan Bennett
9b7384507d Update log messages to include StoreForwardpp 2026-01-01 14:00:40 -06:00
Jonathan Bennett
7d7091ef94 Add null check for p_encrypted before MQTT publish (#9136)
* Add null check for p_encrypted before MQTT publish

A user on BayMesh observed a strange crash in MQTT::onSend that seemed to be a null pointer dereference of this value.

* Trunk
2026-01-01 13:53:53 -06:00
Jonathan Bennett
a5b2d4a9d5 Add null check for p_encrypted before MQTT publish (#9136)
* Add null check for p_encrypted before MQTT publish

A user on BayMesh observed a strange crash in MQTT::onSend that seemed to be a null pointer dereference of this value.

* Trunk
2026-01-01 13:53:36 -06:00
Jonathan Bennett
baccd0c532 Add peers table, check for null 2026-01-01 13:47:50 -06:00
Ben Meadors
7fb95841e4 Apparently I marked board level extra on the wrong tbeam target 2026-01-01 08:25:33 -06:00
Jonathan Bennett
1fecdc7603 Check again for NTP in SF++ 2025-12-31 23:28:36 -06:00
renovate[bot]
eaab8f04b5 chore(deps): update meshtastic/device-ui digest to 940ba85 (#9129)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-01 10:58:56 +11:00
Jonathan Bennett
1625fd88d7 Merge branch 'develop' into sfpp 2025-12-30 20:20:52 -06:00
Jonathan Bennett
fe22460f25 Keep your names consistent! 2025-12-30 20:13:44 -06:00
Jonathan Bennett
f634b7dd60 Don't Scratch! 2025-12-30 20:06:21 -06:00
Jonathan Bennett
f56e651787 Don't shadow variables 2025-12-30 19:10:07 -06:00
Eric Severance
9058ccecf9 Calculate hops correctly even when hop_start==0 (#9120)
* Calculate hops correctly even when hop_start==0.

* Use the same type (int8_t) in the loop, avoiding signed/unsigned mismatches.

* Clarify defaultIfUnknown is returned for encrypted packets.
2025-12-30 19:03:51 -06:00
Ben Meadors
1b83501ee2 Revert "Upgrade all esp32 targets to NimBLE 2.X (#9003)" (#9125)
This reverts commit 40f1f91c0d.
2025-12-30 17:23:50 -06:00
github-actions[bot]
ac571d5dd2 Upgrade trunk (#9121)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-30 07:10:36 -06:00
Jonathan Bennett
55af6c4726 Split message fixes 2025-12-29 23:43:26 -06:00
Jonathan Bennett
d272b28ed4 sfpp Split Messages 2025-12-29 21:57:42 -06:00
Jonathan Bennett
f8c27d1714 Working chain trimming 2025-12-29 18:50:34 -06:00
renovate[bot]
ef30fd850d Update meshtastic/device-ui digest to 7656d49 (#9111)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 19:09:44 +01:00
Jonathan Bennett
25383c9523 Add sfpp as core portnum 2025-12-29 11:43:13 -06:00
Jonathan Bennett
6d90b6536e More comment cleanup 2025-12-29 11:20:39 -06:00
Jonathan Bennett
0759197ab3 Merge branch 'develop' into sfpp 2025-12-29 11:18:43 -06:00
Jonathan Bennett
bbfca12d50 Cleanups 2025-12-29 11:05:21 -06:00
Jonathan Bennett
d44c3a8e1a And the enable 2025-12-29 09:43:02 -06:00
Jonathan Bennett
1cef1094a0 More sfpp config 2025-12-29 09:42:19 -06:00
Jonathan Bennett
02d4ca2983 Payload size work and misc 2025-12-29 09:35:18 -06:00
Jonathan Bennett
36e8a498f1 Wire in COMPRESSED port option pt 1 2025-12-29 09:33:29 -06:00
Jonathan Bennett
d63b583ea2 Hash size 16 2025-12-29 09:32:48 -06:00
Jonathan Bennett
14073e2c9f Merge branch 'develop' into sfpp 2025-12-29 08:28:59 -06:00
renovate[bot]
b9a0015149 chore(deps): update meshtastic/device-ui digest to d234bd9 (#9108)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 06:50:12 -06:00
github-actions[bot]
9673cfb0b2 Upgrade trunk (#9106)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-29 06:03:03 -06:00
renovate[bot]
757f7b68d6 Update meshtastic/device-ui digest to caff403 (#9104)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 13:35:31 +11:00
Jonathan Bennett
39a6ffc664 Merge branch 'develop' into sfpp 2025-12-28 15:45:21 -06:00
Jonathan Bennett
8be790890c Check for read-only DB 2025-12-28 15:29:22 -06:00
Jonathan Bennett
426a7c19dd Short hashes in 2025-12-28 15:29:10 -06:00
Jonathan Bennett
39c0824abb Minor tweak for behavior when chain is empty 2025-12-27 23:28:25 -06:00
Jonathan Bennett
a8a5086b6d Add more SFPP config values 2025-12-27 21:21:51 -06:00
Jason P
5510dae8d3 Implement HAS_PHYSICAL_KEYBOARD for devices with physical keyboards (#9071)
- Implement HAS_PHYSICAL_KEYBOARD for devices with physical keyboards
- Add HAS_PHYSICAL_KEYBOARD to variant.h for:
  - TDeck
  - TLora Pager
  - TDeck Pro
2025-12-27 06:53:55 -06:00
Tom
52fd362720 Fix gps pin defs for various NRF variants. (#9034)
* fix on nrf52_promicro

* try fix for GPS issue

* fix GPS pin assignment in variant.h

* cleared up some comments and confirmed pinouts from schematics

---------

Co-authored-by: macvenez <macvenez@gmail.com>
2025-12-27 06:50:07 -06:00
Jonathan Bennett
428b839254 Merge branch 'develop' into sfpp 2025-12-26 22:22:53 -06:00
github-actions[bot]
33e1f58f6e Upgrade trunk (#9076)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-26 17:45:57 -06:00
Jonathan Bennett
a70d350ce3 Fix getLinkFromCount() 2025-12-26 14:56:27 -06:00
Jonathan Bennett
9dc7ef612e In autoconf, don't probe Wire unless i2c device is set (#9081)
Found another bit of code that crashes my desktop, by probing the wrong i2c bus.
2025-12-26 14:33:17 -06:00
Jonathan Bennett
00a3249c56 Merge branch 'develop' into sfpp 2025-12-26 10:40:03 -06:00
Jonathan Bennett
b51235d4fd Misc 2025-12-26 00:03:21 -06:00
Jonathan Bennett
d07f5be548 misc partial chain fixes 2025-12-25 23:57:21 -06:00
Jonathan Bennett
739ad0dc31 Add count handling to SFPP 2025-12-25 22:39:08 -06:00
Jonathan Bennett
e8fd5174ec Don't stash messages without a matching chain root 2025-12-25 19:38:44 -06:00
github-actions[bot]
b2c82bdc41 Upgrade trunk (#9072)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-25 06:34:38 -06:00
Jonathan Bennett
54a928f47f M6 shutdown and LEDs work (#9065)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-12-24 07:48:14 -06:00
github-actions[bot]
33f18659c8 Upgrade trunk (#9067)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-24 05:20:22 -06:00
github-actions[bot]
3a7093a973 Upgrade trunk (#9047)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-23 18:55:54 -06:00
renovate[bot]
a4f6f4515a Update meshtastic-esp8266-oled-ssd1306 digest to b34c681 (#9062)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-23 18:55:37 -06:00
Jonathan Bennett
96726d22cd Allow UDP and API packets in S$F++ 2025-12-23 11:16:36 -06:00
Jonathan Bennett
3330d297b1 Set hop_limit and hop_start on message rebroadcasts 2025-12-23 11:12:05 -06:00
Jonathan Bennett
e9ed2c0335 Include hop_start in printPacket 2025-12-23 11:11:35 -06:00
Jonathan Bennett
d609d05698 In statusLEDModule, also detect isCharging (#9050) 2025-12-23 07:48:55 -06:00
Jonathan Bennett
3cbc5b7a8d Don't wipe scratch 2025-12-22 09:34:51 -06:00
Jonathan Bennett
20bf822a48 Add missing root_hash from scratch 2025-12-21 20:11:17 -06:00
Jonathan Bennett
14ee1ed075 shorthash on canon announce 2025-12-21 19:58:17 -06:00
Jonathan Bennett
4d48d517e0 Build tryfix next 2025-12-21 19:56:53 -06:00
Jonathan Bennett
ffdb3bc393 Misc fixes 2025-12-21 19:49:00 -06:00
Jonathan Bennett
6e83a9a0b3 unbreak all the targets 2025-12-21 19:39:44 -06:00
Jonathan Bennett
73cfa3c884 Store incoming non-canon messages in scratch 2025-12-21 18:47:05 -06:00
Jonathan Bennett
f2b6383cbb Scratch fix 2025-12-21 16:10:06 -06:00
Jonathan Bennett
28d507f043 Broadcast root hash for an empty chain 2025-12-21 14:25:12 -06:00
Ben Meadors
83c6161ac6 Revert "Automated version bumps (#9025)"
This reverts commit 1021d967da.
2025-12-20 14:10:02 -06:00
Ben Meadors
d93d68d31e Fix -ota.zip in manifest and build output 2025-12-20 14:09:05 -06:00
Jonathan Bennett
d508de9568 Add Store and Forward++ module 2025-12-20 13:05:13 -06:00
163 changed files with 5092 additions and 620 deletions

View File

@@ -20,7 +20,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
RUN apt-get update && apt-get install --no-install-recommends -y \
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \
libusb-1.0-0-dev libssl-dev pkg-config && \
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
pip install --no-cache-dir -U \
platformio==6.1.16 \

View File

@@ -22,7 +22,7 @@ jobs:
### @{fc-author}, Welcome to Meshtastic! :wave:
Thanks for opening your first issue. If it's helpful, an easy way
to get logs is the "Open Serial Monitor" button on the (Web Flasher](https://flasher.meshtastic.org).
to get logs is the "Open Serial Monitor" button on the [Web Flasher](https://flasher.meshtastic.org).
If you have ideas for features, note that we often debate big ideas
in the [discussions tab](https://github.com/meshtastic/firmware/discussions/categories/ideas)

View File

@@ -8,7 +8,9 @@ on:
branches:
- master
- develop
- pioarduino # Remove when merged // use `feature/` in the future.
- event/*
- feature/*
paths-ignore:
- "**.md"
- version.properties
@@ -18,7 +20,9 @@ on:
branches:
- master
- develop
- pioarduino # Remove when merged // use `feature/` in the future.
- event/*
- feature/*
paths-ignore:
- "**.md"
#- "**.yml"

View File

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

View File

@@ -1,51 +0,0 @@
name: Run Trunk Fmt on PR Comment
on:
issue_comment:
types: [created]
permissions: read-all
jobs:
trunk-fmt:
if: github.event.issue.pull_request != null && contains(github.event.comment.body, 'trunk fmt')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Install trunk
run: curl https://get.trunk.io -fsSL | bash
- name: Run Trunk Fmt
run: trunk fmt
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Commit and push changes
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "Add firmware version ${{ steps.version.outputs.long }}"
git push
- name: Comment on PR
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '`trunk fmt` has been run on this PR.'
})

View File

@@ -8,20 +8,20 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.495
- renovate@42.66.8
- checkov@3.2.497
- renovate@42.75.0
- prettier@3.7.4
- trufflehog@3.92.4
- yamllint@1.37.1
- bandit@1.9.2
- trivy@0.68.2
- taplo@0.10.0
- ruff@0.14.10
- ruff@0.14.11
- isort@7.0.0
- markdownlint@0.47.0
- oxipng@10.0.0
- svgo@4.0.0
- actionlint@1.7.9
- actionlint@1.7.10
- flake8@7.3.0
- hadolint@2.14.0
- shfmt@3.6.0

View File

@@ -14,7 +14,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
curl wget g++ zip 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 \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware

View File

@@ -4,8 +4,8 @@
| Firmware Version | Supported |
| ---------------- | ------------------ |
| 2.6.x | :white_check_mark: |
| <= 2.5.x | :x: |
| 2.7.x | :white_check_mark: |
| <= 2.6.x | :x: |
## Reporting a Vulnerability

View File

@@ -11,7 +11,7 @@ RUN apk --no-cache add \
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
libgpiod-dev yaml-cpp-dev bluez-dev \
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
libx11-dev libinput-dev libxkbcommon-dev \
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev \
&& rm -rf /var/cache/apk/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware

View File

@@ -38,4 +38,4 @@ cp bin/device-install.* $OUTDIR/
cp bin/device-update.* $OUTDIR/
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true

View File

@@ -49,4 +49,4 @@ if (echo $1 | grep -q "rak4631"); then
fi
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true

View File

@@ -30,4 +30,4 @@ echo "Copying uf2 file"
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true

View File

@@ -30,4 +30,4 @@ echo "Copying STM32 bin file"
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true

View File

@@ -201,6 +201,16 @@ HostMetrics:
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
StoreAndForward:
# Enabled: true # Enable Store and Forward++, true by default
# DBPath: /var/lib/meshtasticd/ # Path to the S&F++ Sqlite DB
# Stratum0: false # Specify if this node is a Stratum 0 node, the controller node.
# InitialSync: 10 # Number of messages to
# Hops: 3 # Number of hops to use for SF++ messages
# AnnounceInterval: 5 # Interval in minutes between announcing tip of chain hash
# MaxChainLength: 1000 # Maximum number of messages to store in a chain
Config:
# DisplayMode: TWOCOLOR # uncomment to force BaseUI
# DisplayMode: COLOR # uncomment to force MUI

View File

@@ -0,0 +1,11 @@
Lora:
### RAK13300in Slot 1
Module: sx1262
IRQ: 22 #IO6
Reset: 16 # IO4
Busy: 24 # IO5
# Ant_sw: 13 # IO3
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0

View File

@@ -0,0 +1,14 @@
Lora:
Module: sx1262
CS: 0
IRQ: 6
Reset: 2
Busy: 4
RXen: 1
DIO2_AS_RF_SWITCH: true
spidev: ch341
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
USB_PID: 0x5512
USB_VID: 0x1A86
SX126X_MAX_POWER: 22

View File

@@ -87,6 +87,9 @@
</screenshots>
<releases>
<release version="2.7.18" date="2026-01-02">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
</release>
<release version="2.7.17" date="2025-11-28">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17</url>
</release>

View File

@@ -2,11 +2,12 @@
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
import sys
from os.path import join, basename, isfile
from os.path import join
import subprocess
import json
import re
from datetime import datetime
from typing import Dict
from readprops import readProps
@@ -14,8 +15,47 @@ Import("env")
platform = env.PioPlatform()
progname = env.get("PROGNAME")
lfsbin = f"{progname.replace('firmware-', 'littlefs-')}.bin"
manifest_ran = False
def infer_architecture(board_cfg):
try:
mcu = board_cfg.get("build.mcu") if board_cfg else None
except KeyError:
mcu = None
except Exception:
mcu = None
if not mcu:
return None
mcu_l = str(mcu).lower()
if "esp32s3" in mcu_l:
return "esp32-s3"
if "esp32c6" in mcu_l:
return "esp32-c6"
if "esp32c3" in mcu_l:
return "esp32-c3"
if "esp32" in mcu_l:
return "esp32"
if "rp2040" in mcu_l:
return "rp2040"
if "rp2350" in mcu_l:
return "rp2350"
if "nrf52" in mcu_l or "nrf52840" in mcu_l:
return "nrf52840"
if "stm32" in mcu_l:
return "stm32"
return None
def manifest_gather(source, target, env):
global manifest_ran
if manifest_ran:
return
# Skip manifest generation if we cannot determine architecture (host/native builds)
board_arch = infer_architecture(env.BoardConfig())
if not board_arch:
print(f"Skipping mtjson generation for unknown architecture (env={env.get('PIOENV')})")
manifest_ran = True
return
manifest_ran = True
out = []
board_platform = env.BoardConfig().get("platform")
needs_ota_suffix = board_platform == "nordicnrf52"
@@ -47,14 +87,39 @@ def manifest_gather(source, target, env):
manifest_write(out, env)
def manifest_write(files, env):
# Defensive: also skip manifest writing if we cannot determine architecture
def get_project_option(name):
try:
return env.GetProjectOption(name)
except Exception:
return None
def get_project_option_any(names):
for name in names:
val = get_project_option(name)
if val is not None:
return val
return None
def as_bool(val):
return str(val).strip().lower() in ("1", "true", "yes", "on")
def as_int(val):
try:
return int(str(val), 10)
except (TypeError, ValueError):
return None
def as_list(val):
return [item.strip() for item in str(val).split(",") if item.strip()]
manifest = {
"version": verObj["long"],
"build_epoch": build_epoch,
"board": env.get("PIOENV"),
"platformioTarget": env.get("PIOENV"),
"mcu": env.get("BOARD_MCU"),
"repo": repo_owner,
"files": files,
"part": None,
"has_mui": False,
"has_inkhud": False,
}
@@ -69,6 +134,51 @@ def manifest_write(files, env):
if "MESHTASTIC_INCLUDE_INKHUD" in env.get("CPPDEFINES", []):
manifest["has_inkhud"] = True
pioenv = env.get("PIOENV")
device_meta = {}
device_meta_fields = [
("hwModel", ["custom_meshtastic_hw_model"], as_int),
("hwModelSlug", ["custom_meshtastic_hw_model_slug"], str),
("architecture", ["custom_meshtastic_architecture"], str),
("activelySupported", ["custom_meshtastic_actively_supported"], as_bool),
("displayName", ["custom_meshtastic_display_name"], str),
("supportLevel", ["custom_meshtastic_support_level"], as_int),
("images", ["custom_meshtastic_images"], as_list),
("tags", ["custom_meshtastic_tags"], as_list),
("requiresDfu", ["custom_meshtastic_requires_dfu"], as_bool),
("partitionScheme", ["custom_meshtastic_partition_scheme"], str),
("url", ["custom_meshtastic_url"], str),
("key", ["custom_meshtastic_key"], str),
("variant", ["custom_meshtastic_variant"], str),
]
for manifest_key, option_keys, caster in device_meta_fields:
raw_val = get_project_option_any(option_keys)
if raw_val is None:
continue
parsed = caster(raw_val) if callable(caster) else raw_val
if parsed is not None and parsed != "":
device_meta[manifest_key] = parsed
# Determine architecture once; if we can't infer it, skip manifest generation
board_arch = device_meta.get("architecture") or infer_architecture(env.BoardConfig())
if not board_arch:
print(f"Skipping mtjson write for unknown architecture (env={env.get('PIOENV')})")
return
device_meta["architecture"] = board_arch
# Always set requiresDfu: true for nrf52840 targets
if board_arch == "nrf52840":
device_meta["requiresDfu"] = True
device_meta.setdefault("displayName", pioenv)
device_meta.setdefault("activelySupported", False)
if device_meta:
manifest.update(device_meta)
# Write the manifest to the build directory
with open(env.subst("$BUILD_DIR/${PROGNAME}.mt.json"), "w") as f:
json.dump(manifest, f, indent=2)
@@ -166,8 +276,12 @@ def load_boot_logo(source, target, env):
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
env.AddPreAction(f"$BUILD_DIR/{lfsbin}", load_boot_logo)
mtjson_deps = ["buildprog"]
if platform.name == "espressif32":
board_arch = infer_architecture(env.BoardConfig())
should_skip_manifest = board_arch is None
# For host/native envs, avoid depending on 'buildprog' (some targets don't define it)
mtjson_deps = [] if should_skip_manifest else ["buildprog"]
if not should_skip_manifest and platform.name == "espressif32":
# Build littlefs image as part of mtjson target
# Equivalent to `pio run -t buildfs`
target_lfs = env.DataToBin(
@@ -175,11 +289,27 @@ if platform.name == "espressif32":
)
mtjson_deps.append(target_lfs)
env.AddCustomTarget(
name="mtjson",
dependencies=mtjson_deps,
actions=[manifest_gather],
title="Meshtastic Manifest",
description="Generating Meshtastic manifest JSON + Checksums",
always_build=False,
)
if should_skip_manifest:
def skip_manifest(source, target, env):
print(f"mtjson: skipped for native environment: {env.get('PIOENV')}")
env.AddCustomTarget(
name="mtjson",
dependencies=mtjson_deps,
actions=[skip_manifest],
title="Meshtastic Manifest (skipped)",
description="mtjson generation is skipped for native environments",
always_build=True,
)
else:
env.AddCustomTarget(
name="mtjson",
dependencies=mtjson_deps,
actions=[manifest_gather],
title="Meshtastic Manifest",
description="Generating Meshtastic manifest JSON + Checksums",
always_build=True,
)
# Run manifest generation as part of the default build pipeline for non-native builds.
env.Default("mtjson")

View File

@@ -18,8 +18,9 @@ def readProps(prefsLoc):
# Try to find current build SHA if if the workspace is clean. This could fail if git is not installed
try:
# Pin abbreviation length to keep local builds and CI matching (avoid auto-shortening)
sha = (
subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
subprocess.check_output(["git", "rev-parse", "--short=7", "HEAD"])
.decode("utf-8")
.strip()
)

39
boards/t-beam-1w.json Normal file
View File

@@ -0,0 +1,39 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_1W",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "opi",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "t-beam-1w"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino"],
"name": "LilyGo TBeam-1W",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"url": "http://www.lilygo.cn/",
"vendor": "LilyGo"
}

6
debian/changelog vendored
View File

@@ -1,3 +1,9 @@
meshtasticd (2.7.18.0) unstable; urgency=medium
* Version 2.7.18
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Fri, 02 Jan 2026 12:45:36 +0000
meshtasticd (2.7.17.0) unstable; urgency=medium
* Version 2.7.17

3
debian/control vendored
View File

@@ -25,7 +25,8 @@ Build-Depends: debhelper-compat (= 13),
liborcania-dev,
libx11-dev,
libinput-dev,
libxkbcommon-x11-dev
libxkbcommon-x11-dev,
libsqlite3-dev
Standards-Version: 4.6.2
Homepage: https://github.com/meshtastic/firmware
Rules-Requires-Root: no

View File

@@ -39,6 +39,7 @@ BuildRequires: pkgconfig(bluez)
BuildRequires: pkgconfig(libusb-1.0)
BuildRequires: libi2c-devel
BuildRequires: pkgconfig(libuv)
BuildRequires: pkgconfig(sqlite3)
# Web components:
BuildRequires: pkgconfig(openssl)
BuildRequires: pkgconfig(liborcania)

View File

@@ -94,7 +94,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
end2endzone/NonBlockingRTTTL@1.4.0
build_flags = ${env.build_flags} -Os
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/> -<modules/Native/>
; Common libs for communicating over TCP/IP networks such as MQTT
[networking_base]
@@ -119,7 +119,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/862ed040c4ab44f0dfbbe492691f144886102588.zip
https://github.com/meshtastic/device-ui/archive/272defcb35651461830ebfd1b39c9167c8f49317.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]

View File

@@ -1149,11 +1149,11 @@ bool Power::axpChipInit()
PMU->setPowerChannelVoltage(XPOWERS_ALDO1, 3300);
PMU->enablePowerOutput(XPOWERS_ALDO1);
// sdcard power channel
// sdcard (T-Beam S3) / gnns (T-Watch S3 Plus) power channel
PMU->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
#ifndef T_WATCH_S3
PMU->enablePowerOutput(XPOWERS_BLDO1);
#ifdef T_WATCH_S3
#else
// DRV2605 power channel
PMU->setPowerChannelVoltage(XPOWERS_BLDO2, 3300);
PMU->enablePowerOutput(XPOWERS_BLDO2);

View File

@@ -22,12 +22,19 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
// Handle different input events with appropriate buzzer feedback
switch (event->inputEvent) {
#ifdef INPUTDRIVER_ENCODER_TYPE
case INPUT_BROKER_SELECT:
case INPUT_BROKER_SELECT_LONG:
playClick();
break;
#else
case INPUT_BROKER_USER_PRESS:
case INPUT_BROKER_ALT_PRESS:
case INPUT_BROKER_SELECT:
case INPUT_BROKER_SELECT_LONG:
playBeep(); // Confirmation feedback
playBeep();
break;
#endif
case INPUT_BROKER_UP:
case INPUT_BROKER_UP_LONG:
@@ -58,4 +65,4 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
}
return 0; // Allow other handlers to process the event
}
}

View File

@@ -35,6 +35,14 @@ struct ToneDuration {
#define NOTE_G6 1568
#define NOTE_E7 2637
#define NOTE_C4 262
#define NOTE_E4 330
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_C5 523
#define NOTE_E5 659
#define NOTE_G5 784
const int DURATION_1_16 = 62; // 1/16 note
const int DURATION_1_8 = 125; // 1/8 note
const int DURATION_1_4 = 250; // 1/4 note
@@ -65,7 +73,7 @@ void playTones(const ToneDuration *tone_durations, int size)
void playBeep()
{
ToneDuration melody[] = {{NOTE_B3, DURATION_1_8}};
ToneDuration melody[] = {{NOTE_B3, DURATION_1_16}};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
@@ -113,7 +121,14 @@ void playShutdownMelody()
void playChirp()
{
// A short, friendly "chirp" sound for key presses
ToneDuration melody[] = {{NOTE_AS3, 20}}; // Very short AS3 note
ToneDuration melody[] = {{NOTE_AS3, 20}}; // Short AS3 note
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void playClick()
{
// A very short "click" sound with minimum delay; ideal for rotary encoder events
ToneDuration melody[] = {{NOTE_AS3, 1}}; // Very Short AS3
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
@@ -182,3 +197,17 @@ void playComboTune()
};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void play4ClickDown()
{
ToneDuration melody[] = {{NOTE_G5, 55}, {NOTE_E5, 55}, {NOTE_C5, 60}, {NOTE_A4, 55}, {NOTE_G4, 55},
{NOTE_E4, 65}, {NOTE_C4, 80}, {NOTE_G3, 120}, {NOTE_E3, 160}, {NOTE_SILENT, 120}};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void play4ClickUp()
{
// Quick high-pitched notes with trills
ToneDuration melody[] = {{NOTE_F5, 50}, {NOTE_G6, 45}, {NOTE_E7, 60}};
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}

View File

@@ -7,8 +7,11 @@ void playShutdownMelody();
void playGPSEnableBeep();
void playGPSDisableBeep();
void playComboTune();
void play4ClickDown();
void play4ClickUp();
void playBoop();
void playChirp();
void playClick();
void playLongPressLeadUp();
bool playNextLeadUpNote(); // Play the next note in the lead-up sequence
void resetLeadUpSequence(); // Reset the lead-up sequence to start from beginning

View File

@@ -444,6 +444,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#endif
// BME680 BSEC2 support detection
#if !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
#if defined(RAK_4631) || defined(TBEAM_V10)
#define MESHTASTIC_BME680_BSEC2_SUPPORTED 1
#define MESHTASTIC_BME680_HEADER <bsec2.h>
#else
#define MESHTASTIC_BME680_BSEC2_SUPPORTED 0
#define MESHTASTIC_BME680_HEADER <Adafruit_BME680.h>
#endif // defined(RAK_4631)
#endif // !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
// -----------------------------------------------------------------------------
// Global switches to turn off features for a minimized build
// -----------------------------------------------------------------------------

View File

@@ -68,7 +68,7 @@ ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const
if (r == 0x08 || r == 0x00) {
logFoundDevice("SH1106", (uint8_t)addr.address);
o_probe = SCREEN_SH1106; // SH1106
} else if (r == 0x03 || r == 0x04 || r == 0x06 || r == 0x07) {
} else if (r == 0x03 || r == 0x04 || r == 0x06 || r == 0x07 || r == 0x05) {
logFoundDevice("SSD1306", (uint8_t)addr.address);
o_probe = SCREEN_SSD1306; // SSD1306
}
@@ -487,7 +487,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
case TSL25911_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xA0 | 0x12), 1);
if (registerValue == 0x50) {
type = TSL2591;
logFoundDevice("TSL25911", (uint8_t)addr.address);

View File

@@ -934,8 +934,11 @@ void GPS::setPowerPMU(bool on)
// t-beam v1.2 GNSS power channel
on ? PMU->enablePowerOutput(XPOWERS_ALDO3) : PMU->disablePowerOutput(XPOWERS_ALDO3);
} else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE) {
// t-beam-s3-core GNSS power channel
// t-beam-s3-core GNSS power channel
on ? PMU->enablePowerOutput(XPOWERS_ALDO4) : PMU->disablePowerOutput(XPOWERS_ALDO4);
} else if (HW_VENDOR == meshtastic_HardwareModel_T_WATCH_S3) {
// t-watch-s3-plus GNSS power channel
on ? PMU->enablePowerOutput(XPOWERS_BLDO1) : PMU->disablePowerOutput(XPOWERS_BLDO1);
}
} else if (model == XPOWERS_AXP192) {
// t-beam v1.1 GNSS power channel

View File

@@ -148,7 +148,7 @@ bool EInkDisplay::connect()
#endif
#endif
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE) || defined(TTGO_T_ECHO_PLUS)
{
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);

View File

@@ -312,6 +312,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
// Only validate the combined value once
if (rawRGB > 0 && rawRGB <= 255255255) {
LOG_INFO("Setting screen RGB color to user chosen: 0x%06X", rawRGB);
// Extract each component as a normal int first
int r = (rawRGB >> 16) & 0xFF;
int g = (rawRGB >> 8) & 0xFF;
@@ -319,6 +320,16 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
TFT_MESH = COLOR565(static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b));
}
#ifdef TFT_MESH_OVERRIDE
} else if (rawRGB == 0) {
LOG_INFO("Setting screen RGB color to TFT_MESH_OVERRIDE: 0x%04X", TFT_MESH_OVERRIDE);
// Default to TFT_MESH_OVERRIDE if available
TFT_MESH = TFT_MESH_OVERRIDE;
#endif
} else {
// Default best readable yellow color
LOG_INFO("Setting screen RGB color to default: (255,255,128)");
TFT_MESH = COLOR565(255, 255, 128);
}
#if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64)
@@ -1429,10 +1440,15 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
}
nodeDB->updateGUI = false;
break;
case STATUS_TYPE_POWER:
forceDisplay(true);
case STATUS_TYPE_POWER: {
bool currentUSB = powerStatus->getHasUSB();
if (currentUSB != lastPowerUSBState) {
lastPowerUSBState = currentUSB;
forceDisplay(true);
}
break;
}
}
return 0;
}

View File

@@ -715,6 +715,8 @@ class Screen : public concurrency::OSThread
// Whether we are showing the regular screen (as opposed to booth screen or
// Bluetooth PIN screen)
bool showingNormalScreen = false;
/// Track USB power state to only wake screen on actual power state changes
bool lastPowerUSBState = false;
// Implementation to Adjust Brightness
uint8_t brightness = BRIGHTNESS_DEFAULT; // H = 254, MH = 192, ML = 130 L = 103

File diff suppressed because it is too large Load Diff

View File

@@ -128,7 +128,28 @@ template <typename T> struct MenuOption {
MenuOption(const char *labelIn, OptionsAction actionIn) : label(labelIn), action(actionIn), hasValue(false), value() {}
};
struct ScreenColor {
uint8_t r;
uint8_t g;
uint8_t b;
bool useVariant;
ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false)
: r(rIn), g(gIn), b(bIn), useVariant(variantIn)
{
}
};
using RadioPresetOption = MenuOption<meshtastic_Config_LoRaConfig_ModemPreset>;
using LoraRegionOption = MenuOption<meshtastic_Config_LoRaConfig_RegionCode>;
using TimezoneOption = MenuOption<const char *>;
using CompassOption = MenuOption<meshtastic_CompassMode>;
using ScreenColorOption = MenuOption<ScreenColor>;
using GPSToggleOption = MenuOption<meshtastic_Config_PositionConfig_GpsMode>;
using GPSFormatOption = MenuOption<meshtastic_DeviceUIConfig_GpsCoordinateFormat>;
using NodeNameOption = MenuOption<bool>;
using PositionMenuOption = MenuOption<int>;
using ClockFaceOption = MenuOption<bool>;
} // namespace graphics
#endif

View File

@@ -37,6 +37,9 @@ bool ButtonThread::initButton(const ButtonConfig &config)
_activeLow = config.activeLow;
_touchQuirk = config.touchQuirk;
_intRoutine = config.intRoutine;
_pressHandler = config.onPress;
_releaseHandler = config.onRelease;
_suppressLeadUp = config.suppressLeadUpSound;
_longLongPress = config.longLongPress;
userButton = OneButton(config.pinNumber, config.activeLow, config.activePullup);
@@ -133,6 +136,8 @@ int32_t ButtonThread::runOnce()
// Detect start of button press
if (buttonCurrentlyPressed && !buttonWasPressed) {
if (_pressHandler)
_pressHandler();
buttonPressStartTime = millis();
leadUpPlayed = false;
leadUpSequenceActive = false;
@@ -140,7 +145,7 @@ int32_t ButtonThread::runOnce()
}
// Progressive lead-up sound system
if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
if (!_suppressLeadUp && buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
// Start the progressive sequence if not already active
if (!leadUpSequenceActive) {
@@ -160,6 +165,8 @@ int32_t ButtonThread::runOnce()
// Reset when button is released
if (!buttonCurrentlyPressed && buttonWasPressed) {
if (_releaseHandler)
_releaseHandler();
leadUpSequenceActive = false;
resetLeadUpSequence();
}
@@ -241,7 +248,21 @@ int32_t ButtonThread::runOnce()
this->notifyObservers(&evt);
playComboTune();
break;
#if !HAS_SCREEN
case 4:
if (moduleConfig.external_notification.enabled && externalNotificationModule) {
externalNotificationModule->setMute(!externalNotificationModule->getMute());
IF_SCREEN(if (!externalNotificationModule->getMute()) externalNotificationModule->stopNow();)
if (externalNotificationModule->getMute()) {
LOG_INFO("Temporarily Muted");
play4ClickDown(); // Disable tone
} else {
LOG_INFO("Unmuted");
play4ClickUp(); // Enable tone
}
}
break;
#endif
// No valid multipress action
default:
break;

View File

@@ -13,6 +13,9 @@ struct ButtonConfig {
bool activePullup = true;
uint32_t pullupSense = 0;
voidFuncPtr intRoutine = nullptr;
voidFuncPtr onPress = nullptr; // Optional edge callbacks
voidFuncPtr onRelease = nullptr; // Optional edge callbacks
bool suppressLeadUpSound = false;
input_broker_event singlePress = INPUT_BROKER_NONE;
input_broker_event longPress = INPUT_BROKER_NONE;
uint16_t longPressTime = 500;
@@ -94,6 +97,9 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
input_broker_event _shortLong = INPUT_BROKER_NONE;
voidFuncPtr _intRoutine = nullptr;
voidFuncPtr _pressHandler = nullptr;
voidFuncPtr _releaseHandler = nullptr;
bool _suppressLeadUp = false;
uint16_t _longPressTime = 500;
uint16_t _longLongPressTime = 3900;
int _pinNum = 0;

View File

@@ -107,6 +107,10 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
#if defined(BUTTON_PIN_TOUCH)
ButtonThread *TouchButtonThread = nullptr;
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
static bool touchBacklightWasOn = false;
static bool touchBacklightActive = false;
#endif
#endif
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
@@ -205,7 +209,7 @@ ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE,
/// The I2C address of our Air Quality Indicator (if found)
ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef HAS_DRV2605
Adafruit_DRV2605 drv;
#endif
@@ -795,7 +799,6 @@ 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;
#if HAS_TFT
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
tftSetup();
@@ -841,7 +844,12 @@ void setup()
#endif
#endif
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef HAS_DRV2605
#if defined(PIN_DRV_EN)
pinMode(PIN_DRV_EN, OUTPUT);
digitalWrite(PIN_DRV_EN, HIGH);
delay(10);
#endif
drv.begin();
drv.selectLibrary(1);
// I2C trigger by sending 'go' command
@@ -877,7 +885,7 @@ void setup()
SPI.begin();
#endif
#else
// ESP32
// ESP32
#if defined(HW_SPI1_DEVICE)
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
@@ -1046,6 +1054,24 @@ void setup()
};
touchConfig.singlePress = INPUT_BROKER_NONE;
touchConfig.longPress = INPUT_BROKER_BACK;
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
// On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds
touchConfig.longPress = INPUT_BROKER_NONE;
touchConfig.suppressLeadUpSound = true;
touchConfig.onPress = []() {
touchBacklightWasOn = uiconfig.screen_brightness == 1;
if (!touchBacklightWasOn) {
digitalWrite(PIN_EINK_EN, HIGH);
}
touchBacklightActive = true;
};
touchConfig.onRelease = []() {
if (touchBacklightActive && !touchBacklightWasOn) {
digitalWrite(PIN_EINK_EN, LOW);
}
touchBacklightActive = false;
};
#endif
TouchButtonThread->initButton(touchConfig);
#endif

View File

@@ -42,7 +42,7 @@ extern bool eink_found;
extern bool pmu_found;
extern bool isUSBPowered;
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef HAS_DRV2605
#include <Adafruit_DRV2605.h>
extern Adafruit_DRV2605 drv;
#endif

View File

@@ -4,6 +4,7 @@
#include "configuration.h"
#include "mesh-pb-constants.h"
#include "meshUtils.h"
#include "modules/TextMessageModule.h"
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
#include "modules/TraceRouteModule.h"
#endif
@@ -35,6 +36,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
return true; // we handled it, so stop processing
}
if (!seenRecently && !wasUpgraded && textMessageModule) {
seenRecently = textMessageModule->recentlySeen(p->id);
}
if (seenRecently) {
printPacket("Ignore dupe incoming msg", p);
rxDupe++;

View File

@@ -145,6 +145,18 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
tosend->hop_start -= (tosend->hop_limit - 2);
tosend->hop_limit = 2;
}
#elif ARCH_PORTDUINO
if (tosend->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
portduino_config.nohop_ports.size()) {
for (const auto &port : portduino_config.nohop_ports) {
if (port == tosend->decoded.portnum) {
LOG_DEBUG("0-hopping portnum %u", tosend->decoded.portnum);
tosend->hop_start -= tosend->hop_limit;
tosend->hop_limit = 0;
break;
}
}
}
#endif
if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {

View File

@@ -246,7 +246,9 @@ uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool rece
/** The delay to use for retransmitting dropped packets */
uint32_t RadioInterface::getRetransmissionMsec(const meshtastic_MeshPacket *p)
{
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
size_t numbytes = p->which_payload_variant == meshtastic_MeshPacket_decoded_tag
? pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded)
: p->encrypted.size + MESHTASTIC_HEADER_LENGTH;
uint32_t packetAirtime = getPacketTime(numbytes + sizeof(PacketHeader));
// Make sure enough time has elapsed for this packet to be sent and an ACK is received.
// LOG_DEBUG("Waiting for flooding message with airtime %d and slotTime is %d", packetAirtime, slotTimeMsec);
@@ -324,9 +326,9 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
{
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
std::string out =
DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
p->from, p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->channel);
std::string out = DEBUG_PORT.mt_sprintf(
"%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d HopStart=%d Ch=0x%x", prefix, p->id, p->from,
p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->hop_start, p->channel);
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
auto &s = p->decoded;
@@ -520,6 +522,10 @@ void RadioInterface::applyModemConfig()
sf = 12;
break;
}
if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate != cr) {
cr = loraConfig.coding_rate;
LOG_INFO("Using custom Coding Rate %u", cr);
}
} else {
sf = loraConfig.spread_factor;
cr = loraConfig.coding_rate;

View File

@@ -17,6 +17,7 @@
#endif
#include "Default.h"
#if ARCH_PORTDUINO
#include "modules/Native/StoreForwardPlusPlus.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
@@ -113,7 +114,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
// Check 3: role check (moderate cost - multiple comparisons)
if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
continue;
}
@@ -359,6 +360,12 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
abortSendAndNak(encodeResult, p);
return encodeResult; // FIXME - this isn't a valid ErrorCode
}
#if ARCH_PORTDUINO
if (p_decoded->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP &&
(p->from == 0 || p->from == nodeDB->getNodeNum()) && storeForwardPlusPlusModule && portduino_config.sfpp_enabled) {
storeForwardPlusPlusModule->handleEncrypted(p_decoded, p);
}
#endif
#if !MESHTASTIC_EXCLUDE_MQTT
// Only publish to MQTT if we're the original transmitter of the packet
if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) {
@@ -735,6 +742,22 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
cancelSending(p->from, p->id);
skipHandle = true;
}
#if ARCH_PORTDUINO
if (portduino_config.whitelist_enabled) {
bool allowed = false;
for (const auto &port : portduino_config.whitelist_ports) {
if (port == p->decoded.portnum) {
allowed = true;
break;
}
}
if (!allowed) {
LOG_DEBUG("Dropping packet not on Portduino Whitelist");
cancelSending(p->from, p->id);
skipHandle = true;
}
}
#endif
} else {
printPacket("packet decoding failed or skipped (no PSK?)", p);
}
@@ -745,15 +768,19 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
MeshModule::callModules(*p, src);
#if !MESHTASTIC_EXCLUDE_MQTT
// Mark as pki_encrypted if it is not yet decoded and MQTT encryption is also enabled, hash matches and it's a DM not to
// us (because we would be able to decrypt it)
if (decodedState == DecodeState::DECODE_FAILURE && moduleConfig.mqtt.encryption_enabled && p->channel == 0x00 &&
!isBroadcast(p->to) && !isToUs(p))
p_encrypted->pki_encrypted = true;
// After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet
if ((decodedState == DecodeState::DECODE_SUCCESS || p_encrypted->pki_encrypted) && moduleConfig.mqtt.enabled &&
!isFromUs(p) && mqtt)
mqtt->onSend(*p_encrypted, *p, p->channel);
if (p_encrypted == nullptr) {
LOG_WARN("p_encrypted is null, skipping MQTT publish");
} else {
// Mark as pki_encrypted if it is not yet decoded and MQTT encryption is also enabled, hash matches and it's a DM not
// to us (because we would be able to decrypt it)
if (decodedState == DecodeState::DECODE_FAILURE && moduleConfig.mqtt.encryption_enabled && p->channel == 0x00 &&
!isBroadcast(p->to) && !isToUs(p))
p_encrypted->pki_encrypted = true;
// After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet
if ((decodedState == DecodeState::DECODE_SUCCESS || p_encrypted->pki_encrypted) && moduleConfig.mqtt.enabled &&
!isFromUs(p) && mqtt)
mqtt->onSend(*p_encrypted, *p, p->channel);
}
#endif
}

View File

@@ -53,13 +53,26 @@ template <typename T> bool SX126xInterface<T>::init()
#endif
#if defined(USE_GC1109_PA)
// GC1109 FEM chip initialization
// See variant.h for full pin mapping and control logic documentation
// VFEM_Ctrl (LORA_PA_POWER): Power enable for GC1109 LDO (always on)
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
// CSD (LORA_PA_EN): Chip enable - must be HIGH to enable GC1109 for both RX and TX
pinMode(LORA_PA_EN, OUTPUT);
digitalWrite(LORA_PA_EN, LOW);
digitalWrite(LORA_PA_EN, HIGH);
// CPS (LORA_PA_TX_EN): PA mode select - HIGH enables full PA during TX, LOW for RX (don't care)
// Note: TX/RX path switching (CTX) is handled by DIO2 via SX126X_DIO2_AS_RF_SWITCH
pinMode(LORA_PA_TX_EN, OUTPUT);
digitalWrite(LORA_PA_TX_EN, LOW);
digitalWrite(LORA_PA_TX_EN, LOW); // Start in RX-ready state
#endif
#ifdef RF95_FAN_EN
digitalWrite(RF95_FAN_EN, HIGH);
pinMode(RF95_FAN_EN, OUTPUT);
#endif
#if ARCH_PORTDUINO
@@ -85,6 +98,13 @@ template <typename T> bool SX126xInterface<T>::init()
power = -9;
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
#ifdef SX126X_PA_RAMP_US
// Set custom PA ramp time for boards requiring longer stabilization (e.g., T-Beam 1W needs >800us)
if (res == RADIOLIB_ERR_NONE) {
lora.setPaRampTime(SX126X_PA_RAMP_US);
}
#endif
// \todo Display actual typename of the adapter, not just `SX126x`
LOG_INFO("SX126x init result %d", res);
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
@@ -365,13 +385,13 @@ template <typename T> bool SX126xInterface<T>::sleep()
return true;
}
/** Some boards require GPIO control of tx vs rx paths */
/** Control PA mode for GC1109 FEM - CPS pin selects full PA (txon=true) or bypass mode (txon=false) */
template <typename T> void SX126xInterface<T>::setTransmitEnable(bool txon)
{
#if defined(USE_GC1109_PA)
digitalWrite(LORA_PA_POWER, HIGH);
digitalWrite(LORA_PA_EN, HIGH);
digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);
digitalWrite(LORA_PA_POWER, HIGH); // Ensure LDO is on
digitalWrite(LORA_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
#endif
}

View File

@@ -12,6 +12,9 @@ PB_BIND(meshtastic_AdminMessage, meshtastic_AdminMessage, 2)
PB_BIND(meshtastic_AdminMessage_InputEvent, meshtastic_AdminMessage_InputEvent, AUTO)
PB_BIND(meshtastic_AdminMessage_OTAEvent, meshtastic_AdminMessage_OTAEvent, AUTO)
PB_BIND(meshtastic_HamParameters, meshtastic_HamParameters, AUTO)

View File

@@ -113,6 +113,17 @@ typedef struct _meshtastic_AdminMessage_InputEvent {
uint16_t touch_y;
} meshtastic_AdminMessage_InputEvent;
typedef PB_BYTES_ARRAY_T(32) meshtastic_AdminMessage_OTAEvent_ota_hash_t;
/* User is requesting an over the air update.
Node will reboot into the OTA loader */
typedef struct _meshtastic_AdminMessage_OTAEvent {
/* Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now) */
meshtastic_OTAMode reboot_ota_mode;
/* A 32 byte hash of the OTA firmware.
Used to verify the integrity of the firmware before applying an update. */
meshtastic_AdminMessage_OTAEvent_ota_hash_t ota_hash;
} meshtastic_AdminMessage_OTAEvent;
/* Parameters for setting up Meshtastic for ameteur radio usage */
typedef struct _meshtastic_HamParameters {
/* Amateur radio call sign, eg. KD2ABC */
@@ -288,6 +299,8 @@ typedef struct _meshtastic_AdminMessage {
/* Tell the node to reset the nodedb.
When true, favorites are preserved through reset. */
bool nodedb_reset;
/* Tell the node to reset into the OTA Loader */
meshtastic_AdminMessage_OTAEvent ota_request;
};
/* The node generates this key and sends it with any get_x_response packets.
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
@@ -329,6 +342,8 @@ extern "C" {
#define meshtastic_AdminMessage_payload_variant_reboot_ota_mode_ENUMTYPE meshtastic_OTAMode
#define meshtastic_AdminMessage_OTAEvent_reboot_ota_mode_ENUMTYPE meshtastic_OTAMode
@@ -338,12 +353,14 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
#define meshtastic_AdminMessage_OTAEvent_init_default {_meshtastic_OTAMode_MIN, {0, {0}}}
#define meshtastic_HamParameters_init_default {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
#define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}}
#define meshtastic_HamParameters_init_zero {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
@@ -354,6 +371,8 @@ extern "C" {
#define meshtastic_AdminMessage_InputEvent_kb_char_tag 2
#define meshtastic_AdminMessage_InputEvent_touch_x_tag 3
#define meshtastic_AdminMessage_InputEvent_touch_y_tag 4
#define meshtastic_AdminMessage_OTAEvent_reboot_ota_mode_tag 1
#define meshtastic_AdminMessage_OTAEvent_ota_hash_tag 2
#define meshtastic_HamParameters_call_sign_tag 1
#define meshtastic_HamParameters_tx_power_tag 2
#define meshtastic_HamParameters_frequency_tag 3
@@ -422,6 +441,7 @@ extern "C" {
#define meshtastic_AdminMessage_shutdown_seconds_tag 98
#define meshtastic_AdminMessage_factory_reset_config_tag 99
#define meshtastic_AdminMessage_nodedb_reset_tag 100
#define meshtastic_AdminMessage_ota_request_tag 102
#define meshtastic_AdminMessage_session_passkey_tag 101
/* Struct field encoding specification for nanopb */
@@ -481,7 +501,8 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_second
X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \
X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \
X(a, STATIC, SINGULAR, BYTES, session_passkey, 101)
X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102)
#define meshtastic_AdminMessage_CALLBACK NULL
#define meshtastic_AdminMessage_DEFAULT NULL
#define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel
@@ -502,6 +523,7 @@ X(a, STATIC, SINGULAR, BYTES, session_passkey, 101)
#define meshtastic_AdminMessage_payload_variant_store_ui_config_MSGTYPE meshtastic_DeviceUIConfig
#define meshtastic_AdminMessage_payload_variant_add_contact_MSGTYPE meshtastic_SharedContact
#define meshtastic_AdminMessage_payload_variant_key_verification_MSGTYPE meshtastic_KeyVerificationAdmin
#define meshtastic_AdminMessage_payload_variant_ota_request_MSGTYPE meshtastic_AdminMessage_OTAEvent
#define meshtastic_AdminMessage_InputEvent_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, event_code, 1) \
@@ -511,6 +533,12 @@ X(a, STATIC, SINGULAR, UINT32, touch_y, 4)
#define meshtastic_AdminMessage_InputEvent_CALLBACK NULL
#define meshtastic_AdminMessage_InputEvent_DEFAULT NULL
#define meshtastic_AdminMessage_OTAEvent_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, reboot_ota_mode, 1) \
X(a, STATIC, SINGULAR, BYTES, ota_hash, 2)
#define meshtastic_AdminMessage_OTAEvent_CALLBACK NULL
#define meshtastic_AdminMessage_OTAEvent_DEFAULT NULL
#define meshtastic_HamParameters_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, call_sign, 1) \
X(a, STATIC, SINGULAR, INT32, tx_power, 2) \
@@ -544,6 +572,7 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4)
extern const pb_msgdesc_t meshtastic_AdminMessage_msg;
extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg;
extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg;
extern const pb_msgdesc_t meshtastic_HamParameters_msg;
extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg;
extern const pb_msgdesc_t meshtastic_SharedContact_msg;
@@ -552,6 +581,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg
#define meshtastic_AdminMessage_InputEvent_fields &meshtastic_AdminMessage_InputEvent_msg
#define meshtastic_AdminMessage_OTAEvent_fields &meshtastic_AdminMessage_OTAEvent_msg
#define meshtastic_HamParameters_fields &meshtastic_HamParameters_msg
#define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg
#define meshtastic_SharedContact_fields &meshtastic_SharedContact_msg
@@ -560,6 +590,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size
#define meshtastic_AdminMessage_InputEvent_size 14
#define meshtastic_AdminMessage_OTAEvent_size 36
#define meshtastic_AdminMessage_size 511
#define meshtastic_HamParameters_size 31
#define meshtastic_KeyVerificationAdmin_size 25

View File

@@ -92,8 +92,8 @@ typedef enum _meshtastic_HardwareModel {
Less common/prototype boards listed here (needs one more byte over the air)
--------------------------------------------------------------------------- */
meshtastic_HardwareModel_LORA_RELAY_V1 = 32,
/* TODO: REPLACE */
meshtastic_HardwareModel_NRF52840DK = 33,
/* T-Echo Plus device from LilyGo */
meshtastic_HardwareModel_T_ECHO_PLUS = 33,
/* TODO: REPLACE */
meshtastic_HardwareModel_PPR = 34,
/* TODO: REPLACE */
@@ -475,7 +475,10 @@ typedef enum _meshtastic_Routing_Error {
meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED = 37,
/* Airtime fairness rate limit exceeded for a packet
This typically enforced per portnum and is used to prevent a single node from monopolizing airtime */
meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED = 38
meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED = 38,
/* PKI encryption failed, due to no public key for the remote node
This is different from PKI_UNKNOWN_PUBKEY which indicates a failure upon receiving a packet */
meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY = 39
} meshtastic_Routing_Error;
/* Enum of message types */
@@ -1351,8 +1354,12 @@ extern "C" {
#define _meshtastic_Position_AltSource_ARRAYSIZE ((meshtastic_Position_AltSource)(meshtastic_Position_AltSource_ALT_BAROMETRIC+1))
#define _meshtastic_Routing_Error_MIN meshtastic_Routing_Error_NONE
#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED
#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED+1))
#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY
#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY+1))
#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE
#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MAX meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF
#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_ARRAYSIZE ((meshtastic_StoreForwardPlusPlus_SFPP_message_type)(meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF+1))
#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE
#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MAX meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF

View File

@@ -168,7 +168,7 @@ int32_t ExternalNotificationModule::runOnce()
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
#endif
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef HAS_DRV2605
drv.go();
#endif
}
@@ -283,7 +283,7 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
#ifdef UNPHONE
unphone.rgb(red, green, blue);
#endif
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef HAS_DRV2605
if (on) {
drv.go();
} else {
@@ -319,7 +319,7 @@ void ExternalNotificationModule::stopNow()
externalTurnedOn[i] = 0;
}
setIntervalFromNow(0);
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#ifdef HAS_DRV2605
drv.stop();
#endif

View File

@@ -61,6 +61,7 @@
#if ARCH_PORTDUINO
#include "input/LinuxInputImpl.h"
#include "input/SeesawRotary.h"
#include "modules/Native/StoreForwardPlusPlus.h"
#include "modules/Telemetry/HostMetrics.h"
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/StoreForwardModule.h"
@@ -243,6 +244,11 @@ void setupModules()
#endif
#if ARCH_PORTDUINO
new HostMetricsModule();
#if SFPP_ENABLED
if (portduino_config.sfpp_enabled) {
storeForwardPlusPlusModule = new StoreForwardPlusPlusModule();
}
#endif
#endif
#if HAS_TELEMETRY
new DeviceTelemetryModule();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,310 @@
#pragma once
#if __has_include("sqlite3.h")
#define SFPP_ENABLED 1
#include "Channels.h"
#include "ProtobufModule.h"
#include "Router.h"
#include "SinglePortModule.h"
#include "sqlite3.h"
#define SFPP_HASH_SIZE 16
#define SFPP_SHORT_HASH_SIZE 8
/**
* Store and forward ++ module
* There's an obvious need for a store-and-forward mechanism in Meshtastic.
* This module takes heavy inspiration from Git, building a chain of messages that can be synced between nodes.
* Each message is hashed, and the chain is built by hashing the previous commit hash and the current message hash.
* Nodes can request missing messages by requesting the next message after a given commit hash.
*
* The current focus is text messages, limited to the primary channel.
*
* Each chain is identified by a root hash, which is derived from the channelHash, the local nodenum, and the timestamp when
* created.
*
* Each message is also given a message hash, derived from the encrypted payload, the to, from, id.
* Notably not the timestamp, as we want these to match across nodes, even if the timestamps differ.
*
* The authoritative node for the chain will generate a commit hash for each message when adding it to the chain.
* The first message's commit hash is derived from the root hash and the message hash.
* Subsequent messages' commit hashes are derived from the previous commit hash and the current message hash.
* This allows a node to see only the last commit hash, and confirm it hasn't missed any messages.
*
* Nodes can request the next message in the chain by sending a LINK_REQUEST message with the root hash and the last known commit
* hash. Any node that has the next message can respond with a LINK_PROVIDE message containing the next message.
*
* When a satellite node sees a new text message, it stores it in a scratch database.
* These messages are periodically offered to the authoritative node for inclusion in the chain.
*
* The LINK_PROVIDE message does double-duty, sending both on-chain and off-chain messages.
* The differentiator is whether the commit hash is set or left empty.
*
* When a satellite node receives a canonical link message, it checks if it has the message in scratch.
* And evicts it when adding it to the canonical chain.
*
* This approach allows a node to know whether it has seen a given message before, or if it is new coming via SFPP.
* If new, and the timestamp is within the rebroadcast timeout, it will process that message as if it were just received from the
* mesh, allowing it to be decrypted, shown to the user, and rebroadcast.
*/
class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForwardPlusPlus>, private concurrency::OSThread
{
struct link_object {
uint32_t to;
uint32_t from;
uint32_t id;
uint32_t rx_time = 0;
ChannelHash channel_hash;
uint8_t encrypted_bytes[256] = {0};
size_t encrypted_len;
uint8_t message_hash[SFPP_HASH_SIZE] = {0};
size_t message_hash_len = 0;
uint8_t root_hash[SFPP_HASH_SIZE] = {0};
size_t root_hash_len = 0;
uint8_t commit_hash[SFPP_HASH_SIZE] = {0};
size_t commit_hash_len = 0;
uint32_t counter = 0;
std::string payload;
bool validObject = true; // set this false when a chain calulation fails, etc.
};
public:
/** Constructor
*
*/
StoreForwardPlusPlusModule();
/*
-Override the wantPacket method.
*/
virtual bool wantPacket(const meshtastic_MeshPacket *p) override
{
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP ||
p->decoded.portnum == (portduino_config.sfpp_steal_port ? meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP
: meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP)) {
return true;
} else {
return false;
}
}
void handleEncrypted(const meshtastic_MeshPacket *, const meshtastic_MeshPacket *);
protected:
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for
it
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreForwardPlusPlus *t) override;
virtual int32_t runOnce() override;
private:
sqlite3 *ppDb;
sqlite3_stmt *chain_insert_stmt;
sqlite3_stmt *scratch_insert_stmt;
sqlite3_stmt *checkDupMessageHash;
sqlite3_stmt *checkDupCommitHash;
sqlite3_stmt *checkScratch;
sqlite3_stmt *removeScratch;
sqlite3_stmt *updatePayloadStmt;
sqlite3_stmt *getPayloadFromScratchStmt;
sqlite3_stmt *fromScratchStmt;
sqlite3_stmt *fromScratchByHashStmt;
sqlite3_stmt *getNextHashStmt;
sqlite3_stmt *getChainEndStmt;
sqlite3_stmt *getLinkStmt;
sqlite3_stmt *getLinkFromMessageHashStmt;
sqlite3_stmt *getHashFromRootStmt;
sqlite3_stmt *addRootToMappingsStmt;
sqlite3_stmt *getRootFromChannelHashStmt;
sqlite3_stmt *getFullRootHashStmt;
sqlite3_stmt *setChainCountStmt;
sqlite3_stmt *getChainCountStmt;
sqlite3_stmt *getScratchCountStmt;
sqlite3_stmt *getRootCanonScratchCountStmt;
sqlite3_stmt *pruneScratchQueueStmt;
sqlite3_stmt *trimOldestLinkStmt;
sqlite3_stmt *maybeAddPeerStmt;
sqlite3_stmt *getPeerStmt;
sqlite3_stmt *updatePeerStmt;
sqlite3_stmt *clearChainStmt;
sqlite3_stmt *canon_scratch_insert_stmt;
sqlite3_stmt *getCanonScratchCountStmt;
sqlite3_stmt *getCanonScratchStmt;
sqlite3_stmt *removeCanonScratch;
sqlite3_stmt *clearCanonScratchStmt;
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
// returns true if the root hash was found
bool getRootFromChannelHash(ChannelHash, uint8_t *);
// For a given root hash, returns the ChannelHash
// can handle partial root hashes
ChannelHash getChannelHashFromRoot(uint8_t *_root_hash, size_t);
// given a root hash and commit hash, returns the next commit hash in the chain
// can handle partial root and commit hashes, always fills the buffer with 32 bytes
// returns true if a next hash was found
bool getNextHash(uint8_t *, size_t, uint8_t *, size_t, uint8_t *);
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
// but this function will add the root hash if it is not already present
// returns hash size or 0 if not found/added
size_t getOrAddRootFromChannelHash(ChannelHash, uint8_t *);
// adds the ChannelHash and root_hash to the mappings table
void addRootToMappings(ChannelHash, uint8_t *);
// requests the next message in the chain from the mesh network
// Sends a LINK_REQUEST message
void requestNextMessage(uint8_t *, size_t, uint8_t *, size_t);
// request the message X entries from the end.
// used to bootstrap a chain, without downloading all of the history
void requestMessageCount(uint8_t *, size_t, uint32_t);
// sends a LINK_PROVIDE message broadcasting the given link object
void broadcastLink(uint8_t *, size_t);
// sends a LINK_PROVIDE message broadcasting the given link object
void broadcastLink(link_object &, bool, bool = false);
// sends a LINK_PROVIDE message broadcasting the given link object from scratch message store
bool sendFromScratch(uint8_t *);
// Adds the given link object to the canonical chain database
bool addToChain(link_object &);
// Adds an incoming text message to the scratch database
bool addToScratch(link_object &);
// sends a CANON_ANNOUNCE message, specifying the given root and commit hashes
void canonAnnounce(link_object &);
// checks if the message hash is present in the canonical chain database
bool isInDB(uint8_t *, size_t);
// checks if the commit hash is present in the canonical chain database
bool isCommitInDB(uint8_t *, size_t);
// checks if the message hash is present in the scratch database
bool isInScratch(uint8_t *, size_t);
// retrieves a link object from the scratch database
link_object getFromScratch(uint8_t *, size_t);
// removes a link object from the scratch database
void removeFromScratch(uint8_t *, size_t);
// iterate through our scratch database, and see if we can speculate a chain up to the given commit hash
bool speculateScratchChain(uint8_t *, size_t, uint8_t *, uint8_t *);
// retrieves the next link object from scratch given a root hash
link_object getNextScratchObject(uint8_t *);
// fills the payload section with the decrypted data for the given message hash
// probably not needed for production, but useful for testing
void updatePayload(uint8_t *, size_t, std::string);
// Takes the decrypted MeshPacket and the encrypted packet copy, and builds a link_object
// Generates a message hash, but does not set the commit hash
link_object ingestTextPacket(const meshtastic_MeshPacket &, const meshtastic_MeshPacket *);
// ingests a LINK_PROVIDE message and builds a link_object
// confirms the root hash and commit hash
link_object ingestLinkMessage(meshtastic_StoreForwardPlusPlus *);
// retrieves a link object from the canonical chain database given a commit hash
link_object getLink(uint8_t *, size_t);
// retrieves a link object from the canonical chain database given a message hash
link_object getLinkFromMessageHash(uint8_t *, size_t);
// puts the encrypted payload back into the queue as if it were just received
void rebroadcastLinkObject(link_object &);
// Check if an incoming link object's commit hash matches the calculated commit hash
bool checkCommitHash(link_object &lo, uint8_t *commit_hash_bytes, size_t hash_len);
// given a partial root hash, looks up the full 32-byte root hash
// returns true if found
bool lookUpFullRootHash(uint8_t *partial_root_hash, size_t partial_root_hash_len, uint8_t *full_root_hash);
// update the mappings table to set the chain count for the given root hash
void setChainCount(uint8_t *, size_t, uint32_t);
// get the chain count for the given root hash
uint32_t getChainCount(uint8_t *, size_t);
// get the scratch count for the given root hash
uint32_t getScratchCount(uint8_t *, size_t);
// get the canon scratch count for the given root hash
uint32_t getCanonScratchCount(uint8_t *, size_t);
link_object getLinkFromPositionFromTip(uint32_t, uint8_t *, size_t);
void pruneScratchQueue();
void trimOldestLink(uint8_t *, size_t);
void clearChain(uint8_t *, size_t);
void recalculateMessageHash(link_object &);
// given a link object with a payload and other fields, recalculates the message hash
// returns true if a match
bool recalculateHash(link_object &, uint8_t *, size_t, uint8_t *, size_t);
void updatePeers(const meshtastic_MeshPacket &, meshtastic_StoreForwardPlusPlus_SFPP_message_type);
void maybeMoveFromCanonScratch(uint8_t *, size_t);
void addToCanonScratch(link_object &);
link_object getfromCanonScratch(uint8_t *, size_t);
void removeFromCanonScratch(uint8_t *, size_t);
void clearCanonScratch(uint8_t *, size_t, uint32_t);
bool isInCanonScratch(uint8_t *, size_t);
void logLinkObject(link_object &);
// Track if we have a scheduled runOnce pending
// useful to not accudentally delay a scheduled runOnce
bool pendingRun = false;
// Once we have multiple chain types, we can extend this
enum chain_types {
channel_chain = 0,
};
uint32_t rebroadcastTimeout = 3600; // Messages older than this (in seconds) will not be rebroadcast
bool doing_split_send = false;
link_object split_link_out;
bool doing_split_receive = false;
link_object split_link_in;
bool did_announce_last = false;
uint32_t texts_rebroadcast = 0;
uint32_t links_speculated = 0;
uint32_t canon_announces = 0;
uint32_t links_requested = 0;
uint32_t links_provided = 0;
uint32_t links_added = 0;
uint32_t links_from_canon_scratch = 0;
uint32_t links_from_scratch = 0;
uint32_t split_links_sent = 0;
uint32_t split_links_received = 0;
uint32_t links_pruned = 0;
uint32_t scratch_timed_out = 0;
uint32_t sent_from_scratch = 0;
uint32_t received_from_scratch = 0;
};
extern StoreForwardPlusPlusModule *storeForwardPlusPlusModule;
#endif

View File

@@ -63,9 +63,9 @@
SerialModule *serialModule;
SerialModuleRadio *serialModuleRadio;
#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \
defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || \
defined(MUZI_BASE)
#if defined(TTGO_T_ECHO) || defined(TTGO_T_ECHO_PLUS) || defined(CANARYONE) || defined(MESHLINK) || \
defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || \
defined(ELECROW_ThinkNode_M3) || defined(MUZI_BASE)
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial")
{
api_type = TYPE_SERIAL;
@@ -204,8 +204,9 @@ int32_t SerialModule::runOnce()
Serial.begin(baud);
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
}
#elif !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
#elif !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && \
!defined(MUZI_BASE)
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
#ifdef ARCH_RP2040
Serial2.setFIFOSize(RX_BUFFER);
@@ -261,7 +262,7 @@ int32_t SerialModule::runOnce()
}
}
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
processWXSerial();
@@ -536,9 +537,9 @@ ParsedLine parseLine(const char *line)
*/
void SerialModule::processWXSerial()
{
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && \
!defined(ARCH_STM32WL) && !defined(MUZI_BASE)
#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \
!defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \
!defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) && !defined(MUZI_BASE)
static unsigned int lastAveraged = 0;
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
static double dir_sum_sin = 0;

View File

@@ -45,9 +45,9 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event)
// Mute
case INPUT_BROKER_MSG_MUTE_TOGGLE:
if (moduleConfig.external_notification.enabled && externalNotificationModule) {
externalNotificationModule->setMute(externalNotificationModule->getMute());
externalNotificationModule->setMute(!externalNotificationModule->getMute());
IF_SCREEN(if (!externalNotificationModule->getMute()) externalNotificationModule->stopNow(); screen->showSimpleBanner(
externalNotificationModule->getMute() ? "Notifications\nEnabled" : "Notifications\nDisabled", 3000);)
externalNotificationModule->getMute() ? "Notifications\nDisabled" : "Notifications\nEnabled", 3000);)
}
return 0;
// Bluetooth

View File

@@ -53,7 +53,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "Sensor/LTR390UVSensor.h"
#endif
#if __has_include(<bsec2.h>)
#if __has_include(MESHTASTIC_BME680_HEADER)
#include "Sensor/BME680Sensor.h"
#endif
@@ -214,7 +214,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include(<Adafruit_LTR390.h>)
addSensor<LTR390UVSensor>(i2cScanner, ScanI2C::DeviceType::LTR390UV);
#endif
#if __has_include(<bsec2.h>)
#if __has_include(MESHTASTIC_BME680_HEADER)
addSensor<BME680Sensor>(i2cScanner, ScanI2C::DeviceType::BME_680);
#endif
#if __has_include(<Adafruit_BMP280.h>)

View File

@@ -136,12 +136,12 @@ void HealthTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *
display->drawString(x, y += _fontHeight(FONT_SMALL), tempStr);
if (lastMeasurement.variant.health_metrics.has_heart_bpm) {
char heartStr[32];
snprintf(heartStr, sizeof(heartStr), "Heart Rate: %.0f bpm", lastMeasurement.variant.health_metrics.heart_bpm);
snprintf(heartStr, sizeof(heartStr), "Heart Rate: %u bpm", lastMeasurement.variant.health_metrics.heart_bpm);
display->drawString(x, y += _fontHeight(FONT_SMALL), heartStr);
}
if (lastMeasurement.variant.health_metrics.has_spO2) {
char spo2Str[32];
snprintf(spo2Str, sizeof(spo2Str), "spO2: %.0f %%", lastMeasurement.variant.health_metrics.spO2);
snprintf(spo2Str, sizeof(spo2Str), "spO2: %u %%", lastMeasurement.variant.health_metrics.spO2);
display->drawString(x, y += _fontHeight(FONT_SMALL), spo2Str);
}
}

View File

@@ -1,6 +1,6 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<bsec2.h>)
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "BME680Sensor.h"
@@ -10,6 +10,7 @@
BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {}
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
int32_t BME680Sensor::runOnce()
{
if (!bme680.run()) {
@@ -17,10 +18,13 @@ int32_t BME680Sensor::runOnce()
}
return 35;
}
#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
status = 0;
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
if (!bme680.begin(dev->address.address, *bus))
checkStatus("begin");
@@ -42,12 +46,25 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
if (status == 0)
LOG_DEBUG("BME680Sensor::runOnce: bme680.status %d", bme680.status);
#else
bme680 = makeBME680(bus);
if (!bme680->begin(dev->address.address)) {
LOG_ERROR("Init sensor: %s failed at begin()", sensorName);
return status;
}
status = 1;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
initI2CSensor();
return status;
}
bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0)
return false;
@@ -65,9 +82,27 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
// Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms)
measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal;
updateState();
#else
if (!bme680->performReading()) {
LOG_ERROR("BME680Sensor::getMetrics: performReading failed");
return false;
}
measurement->variant.environment_metrics.has_temperature = true;
measurement->variant.environment_metrics.has_relative_humidity = true;
measurement->variant.environment_metrics.has_barometric_pressure = true;
measurement->variant.environment_metrics.has_gas_resistance = true;
measurement->variant.environment_metrics.temperature = bme680->readTemperature();
measurement->variant.environment_metrics.relative_humidity = bme680->readHumidity();
measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F;
measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
return true;
}
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
void BME680Sensor::loadState()
{
#ifdef FSCom
@@ -144,5 +179,6 @@ void BME680Sensor::checkStatus(const char *functionName)
else if (bme680.sensor.status > BME68X_OK)
LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status);
}
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif

View File

@@ -1,23 +1,40 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<bsec2.h>)
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#include <bme68xLibrary.h>
#include <bsec2.h>
#else
#include <Adafruit_BME680.h>
#include <memory>
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis()
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
const uint8_t bsec_config[] = {
#include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt"
};
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
class BME680Sensor : public TelemetrySensor
{
private:
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
Bsec2 bme680;
#else
using BME680Ptr = std::unique_ptr<Adafruit_BME680>;
static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique<Adafruit_BME680>(bus); }
BME680Ptr bme680;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
protected:
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
const char *bsecConfigFileName = "/prefs/bsec.dat";
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
uint8_t accuracy = 0;
@@ -34,10 +51,13 @@ class BME680Sensor : public TelemetrySensor
void loadState();
void updateState();
void checkStatus(const char *functionName);
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
public:
BME680Sensor();
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
virtual int32_t runOnce() override;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};

View File

@@ -17,6 +17,9 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
auto &p = mp.decoded;
LOG_INFO("Received text msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes);
#endif
// add packet ID to the rolling list of packets
textPacketList[textPacketListIndex] = mp.id;
textPacketListIndex = (textPacketListIndex + 1) % TEXT_PACKET_LIST_SIZE;
// We only store/display messages destined for us.
devicestate.rx_text_message = mp;
@@ -47,3 +50,13 @@ bool TextMessageModule::wantPacket(const meshtastic_MeshPacket *p)
{
return MeshService::isTextPayload(p);
}
bool TextMessageModule::recentlySeen(uint32_t id)
{
for (size_t i = 0; i < TEXT_PACKET_LIST_SIZE; i++) {
if (textPacketList[i] != 0 && textPacketList[i] == id) {
return true;
}
}
return false;
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "Observer.h"
#include "SinglePortModule.h"
#define TEXT_PACKET_LIST_SIZE 50
/**
* Text message handling for Meshtastic.
@@ -19,6 +20,8 @@ class TextMessageModule : public SinglePortModule, public Observable<const mesht
*/
TextMessageModule() : SinglePortModule("text", meshtastic_PortNum_TEXT_MESSAGE_APP) {}
bool recentlySeen(uint32_t id);
protected:
/** Called to handle a particular incoming message
*
@@ -27,6 +30,10 @@ class TextMessageModule : public SinglePortModule, public Observable<const mesht
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
virtual bool wantPacket(const meshtastic_MeshPacket *p) override;
private:
uint32_t textPacketList[TEXT_PACKET_LIST_SIZE] = {0};
size_t textPacketListIndex = 0;
};
extern TextMessageModule *textMessageModule;

View File

@@ -14,11 +14,11 @@
#include <atomic>
#include <mutex>
#ifdef NIMBLE_TWO
#include "NimBLEAdvertising.h"
#ifdef CONFIG_BT_NIMBLE_EXT_ADV
#include "NimBLEExtAdvertising.h"
#endif
#include "PowerStatus.h"
#endif
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_gap.h"
@@ -26,12 +26,15 @@
#include "nimble/nimble/host/include/host/ble_gap.h"
#endif
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
namespace
{
constexpr uint16_t kPreferredBleMtu = 517;
constexpr uint16_t kPreferredBleTxOctets = 251;
constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8;
} // namespace
#endif
// Debugging options: careful, they slow things down quite a bit!
// #define DEBUG_NIMBLE_ON_READ_TIMING // uncomment to time onRead duration
@@ -310,9 +313,11 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
{
PhoneAPI::onNowHasData(fromRadioNum);
#ifdef DEBUG_NIMBLE_NOTIFY
int currentNotifyCount = notifyCount.fetch_add(1);
uint8_t cc = bleServer->getConnectedCount();
#ifdef DEBUG_NIMBLE_NOTIFY
// This logging slows things down when there are lots of packets going to the phone, like initial connection:
LOG_DEBUG("BLE notify(%d) fromNum: %d connections: %d", currentNotifyCount, fromRadioNum, cc);
#endif
@@ -321,7 +326,13 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
put_le32(val, fromRadioNum);
fromNumCharacteristic->setValue(val, sizeof(val));
#ifdef NIMBLE_TWO
// NOTE: I don't have any NIMBLE_TWO devices, but this line makes me suspicious, and I suspect it needs to just be
// notify().
fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE);
#else
fromNumCharacteristic->notify();
#endif
}
/// Check the current underlying physical link to see if the client is currently connected
@@ -386,7 +397,12 @@ static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
{
void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &) override
#ifdef NIMBLE_TWO
virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
#else
virtual void onWrite(NimBLECharacteristic *pCharacteristic)
#endif
{
// CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
// Assumption: onWrite is serialized by NimBLE, so we don't need to lock here against multiple concurrent onWrite calls.
@@ -433,7 +449,11 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
{
void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &) override
#ifdef NIMBLE_TWO
virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
#else
virtual void onRead(NimBLECharacteristic *pCharacteristic)
#endif
{
// CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
@@ -541,27 +561,32 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
{
#ifdef NIMBLE_TWO
public:
explicit NimbleBluetoothServerCallback(NimbleBluetooth *ble) : ble(ble) {}
NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; }
private:
NimbleBluetooth *ble;
uint32_t onPassKeyDisplay() override
virtual uint32_t onPassKeyDisplay()
#else
virtual uint32_t onPassKeyRequest()
#endif
{
uint32_t passkey = config.bluetooth.fixed_pin;
if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN) {
LOG_INFO("Use random passkey");
// This is the passkey to be entered on peer - we pick a number >100,000 to ensure 6 digits
passkey = random(100000, 999999);
}
LOG_INFO("*** Enter passkey %06u on the peer side ***", passkey);
LOG_INFO("*** Enter passkey %d on the peer side ***", passkey);
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
meshtastic::BluetoothStatus newStatus(std::to_string(passkey));
bluetoothStatus->updateStatus(&newStatus);
#if HAS_SCREEN
#if HAS_SCREEN // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (screen) {
screen->startAlert([passkey](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
char btPIN[16] = "888888";
@@ -590,29 +615,39 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
});
}
#endif
passkeyShowing = true;
return passkey;
}
void onAuthenticationComplete(NimBLEConnInfo &connInfo) override
#ifdef NIMBLE_TWO
virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo)
#else
virtual void onAuthenticationComplete(ble_gap_conn_desc *desc)
#endif
{
LOG_INFO("BLE authentication complete");
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
bluetoothStatus->updateStatus(&newStatus);
// Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (passkeyShowing) {
passkeyShowing = false;
if (screen) {
if (screen)
screen->endAlert();
}
}
// Store the connection handle for future use
#ifdef NIMBLE_TWO
nimbleBluetoothConnHandle = connInfo.getConnHandle();
#else
nimbleBluetoothConnHandle = desc->conn_handle;
#endif
}
void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override
#ifdef NIMBLE_TWO
virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo)
{
LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
@@ -637,12 +672,21 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
LOG_INFO("BLE conn %u initial MTU %u (target %u)", connHandle, connInfo.getMTU(), kPreferredBleMtu);
pServer->updateConnParams(connHandle, 6, 12, 0, 200);
}
#endif
void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override
#ifdef NIMBLE_TWO
virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason)
{
LOG_INFO("BLE disconnect reason: %d", reason);
#else
virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc)
{
LOG_INFO("BLE disconnect");
#endif
#ifdef NIMBLE_TWO
if (ble->isDeInit)
return;
#endif
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newStatus);
@@ -666,69 +710,35 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
bluetoothPhoneAPI->writeCount = 0;
}
// Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
memset(lastToRadio, 0, sizeof(lastToRadio));
nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE;
nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; // BLE_HS_CONN_HANDLE_NONE means "no connection"
#ifdef NIMBLE_TWO
// Restart Advertising
ble->startAdvertising();
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
if (!pAdvertising->start(0)) {
if (pAdvertising->isAdvertising()) {
LOG_DEBUG("BLE advertising already running");
} else {
LOG_ERROR("BLE failed to restart advertising");
}
}
#endif
}
};
static NimbleBluetoothToRadioCallback *toRadioCallbacks;
static NimbleBluetoothFromRadioCallback *fromRadioCallbacks;
void NimbleBluetooth::startAdvertising()
{
#if defined(CONFIG_BT_NIMBLE_EXT_ADV)
NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
NimBLEExtAdvertisement legacyAdvertising;
legacyAdvertising.setLegacyAdvertising(true);
legacyAdvertising.setScannable(true);
legacyAdvertising.setConnectable(true);
legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN);
if (powerStatus->getHasBattery() == 1) {
legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f));
}
legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID));
legacyAdvertising.setMinInterval(500);
legacyAdvertising.setMaxInterval(1000);
NimBLEExtAdvertisement legacyScanResponse;
legacyScanResponse.setLegacyAdvertising(true);
legacyScanResponse.setConnectable(true);
legacyScanResponse.setName(getDeviceName());
if (!pAdvertising->setInstanceData(0, legacyAdvertising)) {
LOG_ERROR("BLE failed to set legacyAdvertising");
} else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) {
LOG_ERROR("BLE failed to set legacyScanResponse");
} else if (!pAdvertising->start(0, 0, 0)) {
LOG_ERROR("BLE failed to start legacyAdvertising");
}
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
pAdvertising->addServiceUUID(MESH_SERVICE_UUID);
if (powerStatus->getHasBattery() == 1) {
pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f));
}
NimBLEAdvertisementData scan;
scan.setName(getDeviceName());
pAdvertising->setScanResponseData(scan);
pAdvertising->enableScanResponse(true);
if (!pAdvertising->start(0)) {
LOG_ERROR("BLE failed to start advertising");
}
#endif
LOG_DEBUG("BLE Advertising started");
}
void NimbleBluetooth::shutdown()
{
// No measurable power saving for ESP32 during light-sleep(?)
#ifndef ARCH_ESP32
// Shutdown bluetooth for minimum power draw
LOG_INFO("Disable bluetooth");
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
@@ -736,6 +746,7 @@ void NimbleBluetooth::shutdown()
#endif
}
// Proper shutdown for ESP32. Needs reboot to reverse.
void NimbleBluetooth::deinit()
{
#ifdef ARCH_ESP32
@@ -749,17 +760,21 @@ void NimbleBluetooth::deinit()
digitalWrite(BLE_LED, LOW);
#endif
#endif
#ifndef NIMBLE_TWO
NimBLEDevice::deinit();
#endif
#endif
}
// Has initial setup been completed
bool NimbleBluetooth::isActive()
{
return bleServer != nullptr;
return bleServer;
}
bool NimbleBluetooth::isConnected()
{
return bleServer && bleServer->getConnectedCount() > 0;
return bleServer->getConnectedCount() > 0;
}
int NimbleBluetooth::getRssi()
@@ -803,7 +818,7 @@ void NimbleBluetooth::setup()
LOG_INFO("Init the NimBLE bluetooth module");
NimBLEDevice::init(getDeviceName());
NimBLEDevice::setPower(9);
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6))
int mtuResult = NimBLEDevice::setMTU(kPreferredBleMtu);
@@ -836,7 +851,11 @@ void NimbleBluetooth::setup()
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
}
bleServer = NimBLEDevice::createServer();
auto *serverCallbacks = new NimbleBluetoothServerCallback(this);
#ifdef NIMBLE_TWO
NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this);
#else
NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback();
#endif
bleServer->setCallbacks(serverCallbacks, true);
setupService();
startAdvertising();
@@ -881,7 +900,11 @@ void NimbleBluetooth::setupService()
NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic)
(uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1);
#ifdef NIMBLE_TWO
NimBLE2904 *batteryLevelDescriptor = BatteryCharacteristic->create2904();
#else
NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904);
#endif
batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8);
batteryLevelDescriptor->setNamespace(1);
batteryLevelDescriptor->setUnit(0x27ad);
@@ -889,12 +912,54 @@ void NimbleBluetooth::setupService()
batteryService->start();
}
void NimbleBluetooth::startAdvertising()
{
#ifdef NIMBLE_TWO
NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
NimBLEExtAdvertisement legacyAdvertising;
legacyAdvertising.setLegacyAdvertising(true);
legacyAdvertising.setScannable(true);
legacyAdvertising.setConnectable(true);
legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN);
if (powerStatus->getHasBattery() == 1) {
legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f));
}
legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID));
legacyAdvertising.setMinInterval(500);
legacyAdvertising.setMaxInterval(1000);
NimBLEExtAdvertisement legacyScanResponse;
legacyScanResponse.setLegacyAdvertising(true);
legacyScanResponse.setConnectable(true);
legacyScanResponse.setName(getDeviceName());
if (!pAdvertising->setInstanceData(0, legacyAdvertising)) {
LOG_ERROR("BLE failed to set legacyAdvertising");
} else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) {
LOG_ERROR("BLE failed to set legacyScanResponse");
} else if (!pAdvertising->start(0, 0, 0)) {
LOG_ERROR("BLE failed to start legacyAdvertising");
}
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
pAdvertising->addServiceUUID(MESH_SERVICE_UUID);
pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
pAdvertising->start(0);
#endif
}
/// Given a level between 0-100, update the BLE attribute
void updateBatteryLevel(uint8_t level)
{
if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) {
BatteryCharacteristic->setValue(&level, 1);
#ifdef NIMBLE_TWO
BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE);
#else
BatteryCharacteristic->notify();
#endif
}
}
@@ -909,7 +974,11 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
if (!bleServer || !isConnected() || length > 512) {
return;
}
#ifdef NIMBLE_TWO
logRadioCharacteristic->notify(logMessage, length, BLE_HS_CONN_HANDLE_NONE);
#else
logRadioCharacteristic->notify(logMessage, length, true);
#endif
}
void clearNVS()

View File

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

View File

@@ -66,6 +66,8 @@
#define HW_VENDOR meshtastic_HardwareModel_T_ECHO
#elif defined(T_ECHO_LITE)
#define HW_VENDOR meshtastic_HardwareModel_T_ECHO_LITE
#elif defined(TTGO_T_ECHO_PLUS)
#define HW_VENDOR meshtastic_HardwareModel_T_ECHO_PLUS
#elif defined(ELECROW_ThinkNode_M1)
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M1
#elif defined(ELECROW_ThinkNode_M3)

View File

@@ -6,6 +6,7 @@
#include "target_specific.h"
#include "PortduinoGlue.h"
#include "SHA256.h"
#include "api/ServerAPI.h"
#include "linux/gpio/LinuxGPIOPin.h"
#include "meshUtils.h"
@@ -176,6 +177,7 @@ void portduinoSetup()
if (portduino_config.force_simradio == true) {
portduino_config.lora_module = use_simradio;
portduino_config.sfpp_enabled = false;
} else if (configPath != nullptr) {
if (loadConfig(configPath)) {
if (!yamlOnly)
@@ -270,7 +272,39 @@ void portduinoSetup()
}
std::cout << "autoconf: Found Pi HAT+ " << hat_vendor << " " << autoconf_product << " at /proc/device-tree/hat"
<< std::endl;
found_hat = true;
// potential TODO: Validate that this is a real UUID
std::ifstream hatUUID("/proc/device-tree/hat/uuid");
char uuid[38] = {0};
if (hatUUID.is_open()) {
hatUUID.read(uuid, 37);
hatUUID.close();
std::cout << "autoconf: UUID " << uuid << std::endl;
SHA256 uuid_hash;
uint8_t uuid_hash_bytes[32] = {0};
uuid_hash.reset();
uuid_hash.update(uuid, 37);
uuid_hash.finalize(uuid_hash_bytes, 32);
for (int j = 0; j < 16; j++) {
portduino_config.device_id[j] = uuid_hash_bytes[j];
}
portduino_config.has_device_id = true;
uint8_t dmac[6] = {0};
dmac[0] = (uuid_hash_bytes[17] << 4) | 2;
dmac[1] = uuid_hash_bytes[18];
dmac[2] = uuid_hash_bytes[19];
dmac[3] = uuid_hash_bytes[20];
dmac[4] = uuid_hash_bytes[21];
dmac[5] = uuid_hash_bytes[22];
char macBuf[13] = {0};
snprintf(macBuf, sizeof(macBuf), "%02X%02X%02X%02X%02X%02X", dmac[0], dmac[1], dmac[2], dmac[3], dmac[4],
dmac[5]);
portduino_config.mac_address = macBuf;
found_hat = true;
}
} else {
std::cout << "autoconf: Could not locate Pi HAT+ at /proc/device-tree/hat" << std::endl;
}
@@ -366,6 +400,14 @@ void portduinoSetup()
cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml");
} else if (found_ch341) {
product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml");
// look for more data after the null terminator
size_t len = strlen(autoconf_product);
if (len < 74) {
memcpy(portduino_config.device_id, autoconf_product + len + 1, 16);
if (!memfll(portduino_config.device_id, '\0', 16) && !memfll(portduino_config.device_id, 0xff, 16)) {
portduino_config.has_device_id = true;
}
}
}
// Don't try to automatically find config for a device with RAK eeprom.
@@ -411,9 +453,11 @@ void portduinoSetup()
ch341Hal->getProductString(product_string, 95);
std::cout << "CH341 Product " << product_string << std::endl;
if (strlen(serial) == 8 && portduino_config.mac_address.length() < 12) {
uint8_t hash[32] = {0};
std::cout << "Deriving MAC address from Serial and Product String" << std::endl;
uint8_t hash[104] = {0};
memcpy(hash, serial, 8);
crypto->hash(hash, 8);
memcpy(hash + 8, product_string, strlen(product_string));
crypto->hash(hash, 8 + strlen(product_string));
dmac[0] = (hash[0] << 4) | 2;
dmac[1] = hash[1];
dmac[2] = hash[2];
@@ -788,6 +832,28 @@ bool loadConfig(const char *configPath)
}
}
if (yamlConfig["StoreAndForward"]) {
portduino_config.sfpp_stratum0 = (yamlConfig["StoreAndForward"]["Stratum0"]).as<bool>(false);
portduino_config.sfpp_enabled = (yamlConfig["StoreAndForward"]["Enabled"]).as<bool>(true);
portduino_config.sfpp_db_path = (yamlConfig["StoreAndForward"]["DBPath"]).as<std::string>("/var/lib/meshtasticd/");
portduino_config.sfpp_initial_sync = (yamlConfig["StoreAndForward"]["InitialSync"]).as<int>(10);
portduino_config.sfpp_hops = (yamlConfig["StoreAndForward"]["Hops"]).as<int>(3);
portduino_config.sfpp_announce_interval = (yamlConfig["StoreAndForward"]["AnnounceInterval"]).as<int>(5);
portduino_config.sfpp_max_chain = (yamlConfig["StoreAndForward"]["MaxChainLength"]).as<uint32_t>(1000);
portduino_config.sfpp_backlog_limit = (yamlConfig["StoreAndForward"]["BacklogLimit"]).as<uint32_t>(100);
portduino_config.sfpp_steal_port = (yamlConfig["StoreAndForward"]["StealPort"]).as<bool>(false);
}
if (yamlConfig["Routing"]) {
if (yamlConfig["Routing"]["WhitelistPorts"]) {
portduino_config.whitelist_ports = (yamlConfig["Routing"]["WhitelistPorts"]).as<std::vector<int>>();
if (portduino_config.whitelist_ports.size() > 0) {
portduino_config.whitelist_enabled = true;
}
}
if (yamlConfig["Routing"]["NoHopPorts"]) {
portduino_config.nohop_ports = (yamlConfig["Routing"]["NoHopPorts"]).as<std::vector<int>>();
}
}
if (yamlConfig["General"]) {
portduino_config.MaxNodes = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
portduino_config.maxtophone = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);

View File

@@ -2,6 +2,7 @@
#include <fstream>
#include <map>
#include <unordered_map>
#include <vector>
#include "LR11x0Interface.h"
#include "Module.h"
@@ -169,6 +170,26 @@ extern struct portduino_config_struct {
int configDisplayMode = 0;
bool has_configDisplayMode = false;
// Store and Forward++
std::string sfpp_db_path = "/var/lib/meshtasticd/";
bool sfpp_stratum0 = false;
bool sfpp_enabled = true;
bool sfpp_steal_port = false;
int sfpp_initial_sync = 10;
int sfpp_hops = 3;
int sfpp_announce_interval = 5; // minutes
uint32_t sfpp_max_chain = 1000;
uint32_t sfpp_backlog_limit = 100;
// allowed root hashes
// upstream node
// Are we allowing unknown channel hashes? Does this even make sense?
// Allow DMs
// Routing
bool whitelist_enabled = false;
std::vector<int> whitelist_ports = {};
std::vector<int> nohop_ports = {};
// General
std::string mac_address = "";
bool mac_address_explicit = false;
@@ -488,6 +509,29 @@ extern struct portduino_config_struct {
out << YAML::EndMap; // Config
}
// StoreAndForward
if (sfpp_enabled) {
out << YAML::Key << "StoreAndForward" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "Enabled" << YAML::Value << sfpp_enabled;
out << YAML::Key << "DBPath" << YAML::Value << sfpp_db_path;
out << YAML::Key << "Stratum0" << YAML::Value << sfpp_stratum0;
out << YAML::Key << "InitialSync" << YAML::Value << sfpp_initial_sync;
out << YAML::Key << "Hops" << YAML::Value << sfpp_hops;
out << YAML::Key << "AnnounceInterval" << YAML::Value << sfpp_announce_interval;
out << YAML::Key << "BacklogLimit" << YAML::Value << sfpp_backlog_limit;
out << YAML::Key << "MaxChainLength" << YAML::Value << sfpp_max_chain;
out << YAML::Key << "StealPort" << YAML::Value << sfpp_steal_port;
out << YAML::EndMap; // StoreAndForward
}
// Routing
if (whitelist_enabled || nohop_ports.size() > 0) {
out << YAML::Key << "Routing" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "WhitelistPorts" << YAML::Value << whitelist_ports;
out << YAML::Key << "NoHopPorts" << YAML::Value << nohop_ports;
out << YAML::EndMap; // Routing
}
// General
out << YAML::Key << "General" << YAML::Value << YAML::BeginMap;
if (config_directory != "")

View File

@@ -64,7 +64,7 @@ class Ch341Hal : public RadioLibHal
void getProductString(char *_product_string, size_t len)
{
len = len > 95 ? 95 : len;
strncpy(_product_string, pinedio.product_string, len);
memcpy(_product_string, pinedio.product_string, len);
}
void init() override {}

View File

@@ -1,5 +1,12 @@
; Port to Disaster Radio's ESP32-v3 Dev Board
[env:meshtastic-dr-dev]
custom_meshtastic_hw_model = 41
custom_meshtastic_hw_model_slug = DR_DEV
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = false
custom_meshtastic_display_name = DR-DEV
custom_meshtastic_tags = DIY
extends = esp32_base
board = esp32doit-devkit-v1
board_level = extra

View File

@@ -1,5 +1,13 @@
; Hydra - Meshtastic DIY v1 hardware with some specific changes
[env:hydra]
custom_meshtastic_hw_model = 39
custom_meshtastic_hw_model_slug = HYDRA
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = Hydra
custom_meshtastic_tags = DIY
extends = esp32_base
board = esp32doit-devkit-v1
build_flags =

View File

@@ -1,5 +1,14 @@
; Meshtastic DIY v1 by Nano VHF Schematic based on ESP32-WROOM-32 (38 pins) devkit & EBYTE E22 SX1262/SX1268 module
[env:meshtastic-diy-v1]
custom_meshtastic_hw_model = 39
custom_meshtastic_hw_model_slug = DIY_V1
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = DIY V1
custom_meshtastic_images = diy.svg
custom_meshtastic_tags = DIY
extends = esp32_base
board = esp32doit-devkit-v1
board_check = true

View File

@@ -38,7 +38,6 @@ build_flags =
-DAXP_DEBUG_PORT=Serial
-DCONFIG_BT_NIMBLE_ENABLED
-DCONFIG_BT_NIMBLE_MAX_BONDS=6 # default is 3
-DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
-DCONFIG_BT_NIMBLE_MAX_CCCDS=20
-DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
@@ -61,11 +60,11 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master
https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
h2zero/NimBLE-Arduino@2.3.7
h2zero/NimBLE-Arduino@^1.4.3
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.2
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

View File

@@ -5,4 +5,4 @@ extends = esp32_common
custom_esp32_kind = esp32
build_flags =
${esp32_common.build_flags}
${esp32_common.build_flags}

View File

@@ -1,4 +1,11 @@
[env:heltec-v1]
custom_meshtastic_hw_model = 11
custom_meshtastic_hw_model_slug = HELTEC_V1
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = false
custom_meshtastic_display_name = Heltec V1
custom_meshtastic_tags = Heltec
;build_type = debug ; to make it possible to step through our jtag debugger
extends = esp32_base
board_level = extra

View File

@@ -1,4 +1,11 @@
[env:heltec-v2_1]
custom_meshtastic_hw_model = 10
custom_meshtastic_hw_model_slug = HELTEC_V2_1
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = false
custom_meshtastic_display_name = Heltec V2.1
custom_meshtastic_tags = Heltec
board_level = extra
;build_type = debug ; to make it possible to step through our jtag debugger
extends = esp32_base

View File

@@ -1,4 +1,11 @@
[env:heltec-v2_0]
custom_meshtastic_hw_model = 5
custom_meshtastic_hw_model_slug = HELTEC_V2_0
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = false
custom_meshtastic_display_name = Heltec V2.0
custom_meshtastic_tags = Heltec
;build_type = debug ; to make it possible to step through our jtag debugger
board_level = extra
extends = esp32_base

View File

@@ -1,4 +1,12 @@
[env:m5stack-core]
custom_meshtastic_hw_model = 42
custom_meshtastic_hw_model_slug = M5STACK
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = M5 Stack
custom_meshtastic_tags = M5Stack
extends = esp32_base
board = m5stack-core-esp32
monitor_filters = esp32_exception_decoder

View File

@@ -1,5 +1,13 @@
; The 1.0 release of the nano-g1-explorer board
[env:nano-g1-explorer]
custom_meshtastic_hw_model = 17
custom_meshtastic_hw_model_slug = NANO_G1_EXPLORER
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = Nano G1 Explorer
custom_meshtastic_tags = B&Q
extends = esp32_base
board = ttgo-t-beam
build_flags =

View File

@@ -1,5 +1,13 @@
; The 1.0 release of the nano-g1 board
[env:nano-g1]
custom_meshtastic_hw_model = 14
custom_meshtastic_hw_model_slug = NANO_G1
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = Nano G1
custom_meshtastic_tags = B&Q
extends = esp32_base
board = ttgo-t-beam
build_flags =

View File

@@ -1,4 +1,12 @@
[env:radiomaster_900_bandit_nano]
custom_meshtastic_hw_model = 64
custom_meshtastic_hw_model_slug = RADIOMASTER_900_BANDIT_NANO
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 2
custom_meshtastic_display_name = RadioMaster 900 Bandit Nano
custom_meshtastic_tags = RadioMaster
extends = esp32_base
board = esp32doit-devkit-v1
build_flags =

View File

@@ -1,4 +1,13 @@
[env:rak11200]
custom_meshtastic_hw_model = 13
custom_meshtastic_hw_model_slug = RAK11200
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = RAK WisBlock 11200
custom_meshtastic_images = rak11200.svg
custom_meshtastic_tags = RAK
extends = esp32_base
board = wiscore_rak11200
board_level = pr

View File

@@ -1,5 +1,13 @@
; The 1.0 release of the nano-g1 board
[env:station-g1]
custom_meshtastic_hw_model = 25
custom_meshtastic_hw_model_slug = STATION_G1
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = Station G1
custom_meshtastic_tags = B&Q
extends = esp32_base
board = ttgo-t-beam
build_flags =

View File

@@ -1,8 +1,17 @@
; The 1.0 release of the TBEAM board
[env:tbeam]
custom_meshtastic_hw_model = 4
custom_meshtastic_hw_model_slug = TBEAM
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = LILYGO T-Beam
custom_meshtastic_images = tbeam.svg
custom_meshtastic_tags = LilyGo
extends = esp32_base
board = ttgo-t-beam
board_level = extra
board_check = true
build_flags = ${esp32_base.build_flags}
-D TBEAM_V10
@@ -13,7 +22,7 @@ upload_speed = 921600
[env:tbeam-displayshield]
extends = env:tbeam
board_level = extra
build_flags =
${env:tbeam.build_flags}
-D USE_ST7796

View File

@@ -1,5 +1,12 @@
; The original TBEAM board without the AXP power chip and a few other changes
[env:tbeam0_7]
custom_meshtastic_hw_model = 6
custom_meshtastic_hw_model_slug = TBEAM_V0P7
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = false
custom_meshtastic_display_name = LILYGO T-Beam V0.7
custom_meshtastic_tags = LilyGo
board_level = extra
extends = esp32_base
board = ttgo-t-beam

View File

@@ -1,4 +1,11 @@
[env:tlora-v1]
custom_meshtastic_hw_model = 2
custom_meshtastic_hw_model_slug = TLORA_V1
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = false
custom_meshtastic_display_name = LILYGO T-LoRa V1
custom_meshtastic_tags = LilyGo
board_level = extra
extends = esp32_base
board = ttgo-lora32-v1

View File

@@ -1,4 +1,11 @@
[env:tlora-v2]
custom_meshtastic_hw_model = 1
custom_meshtastic_hw_model_slug = TLORA_V2
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = false
custom_meshtastic_display_name = LILYGO T-LoRa V2
custom_meshtastic_tags = LilyGo
board_level = extra
extends = esp32_base
board = ttgo-lora32-v1

View File

@@ -1,4 +1,13 @@
[env:tlora-v2-1-1_6]
custom_meshtastic_hw_model = 3
custom_meshtastic_hw_model_slug = TLORA_V2_1_1P6
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = LILYGO T-LoRa V2.1-1.6
custom_meshtastic_images = tlora-v2-1-1_6.svg
custom_meshtastic_tags = LilyGo
extends = esp32_base
board = ttgo-lora32-v21
board_check = true

View File

@@ -1,4 +1,13 @@
[env:tlora-v2-1-1_8]
custom_meshtastic_hw_model = 15
custom_meshtastic_hw_model_slug = TLORA_V2_1_1P8
custom_meshtastic_architecture = esp32
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = LILYGO T-LoRa V2.1-1.8
custom_meshtastic_images = tlora-v2-1-1_8.svg
custom_meshtastic_tags = LilyGo, 2.4GHz
extends = esp32_base
board_level = extra
board = ttgo-lora32-v21

View File

@@ -4,8 +4,3 @@ custom_esp32_kind = esp32c3
monitor_speed = 115200
monitor_filters = esp32_c3_exception_decoder
build_flags =
${esp32_common.build_flags}
-DCONFIG_BT_NIMBLE_EXT_ADV=1
-DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2

View File

@@ -1,4 +1,13 @@
[env:heltec-ht62-esp32c3-sx1262]
custom_meshtastic_hw_model = 53
custom_meshtastic_hw_model_slug = HELTEC_HT62
custom_meshtastic_architecture = esp32-c3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Heltec HT62
custom_meshtastic_images = heltec-ht62-esp32c3-sx1262.svg
custom_meshtastic_tags = Heltec
extends = esp32c3_base
board = esp32-c3-devkitm-1
board_level = pr

View File

@@ -1,4 +1,13 @@
[env:m5stack-unitc6l]
custom_meshtastic_hw_model = 111
custom_meshtastic_hw_model_slug = M5STACK_C6L
custom_meshtastic_architecture = esp32-c6
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = M5Stack Unit C6L
custom_meshtastic_images = m5_c6l.svg
custom_meshtastic_tags = M5Stack
extends = esp32c6_base
board = esp32-c6-devkitc-1
;OpenOCD flash method
@@ -26,6 +35,7 @@ build_flags =
-D HAS_BLUETOOTH=1
-DCONFIG_BT_NIMBLE_EXT_ADV=1
-DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2
-D NIMBLE_TWO
monitor_speed=115200
lib_ignore =
NonBlockingRTTTL

View File

@@ -1,4 +1,13 @@
[env:CDEBYTE_EoRa-S3]
custom_meshtastic_hw_model = 61
custom_meshtastic_hw_model_slug = CDEBYTE_EORA_S3
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 3
custom_meshtastic_display_name = EBYTE EoRa-S3
custom_meshtastic_tags = EByte
custom_meshtastic_requires_dfu = true
extends = esp32s3_base
board = CDEBYTE_EoRa-S3
build_flags =

View File

@@ -1,4 +1,14 @@
[env:thinknode_m2]
custom_meshtastic_hw_model = 90
custom_meshtastic_hw_model_slug = THINKNODE_M2
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = ThinkNode M2
custom_meshtastic_images = thinknode_m2.svg
custom_meshtastic_tags = Elecrow
custom_meshtastic_requires_dfu = false
extends = esp32s3_base
board = ESP32-S3-WROOM-1-N4
build_flags =

View File

@@ -1,4 +1,14 @@
[env:thinknode_m5]
custom_meshtastic_hw_model = 107
custom_meshtastic_hw_model_slug = THINKNODE_M5
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = ThinkNode M5
custom_meshtastic_images = thinknode_m1.svg
custom_meshtastic_tags = Elecrow
custom_meshtastic_requires_dfu = false
extends = esp32s3_base
board = ESP32-S3-WROOM-1-N4
build_flags =

View File

@@ -75,6 +75,17 @@ build_flags =
-D DISPLAY_SET_RESOLUTION
[env:elecrow-adv-24-28-tft]
custom_meshtastic_hw_model = 97
custom_meshtastic_hw_model_slug = CROWPANEL
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Crowpanel Adv 2.4/2.8 TFT
custom_meshtastic_images = crowpanel_2_4.svg, crowpanel_2_8.svg
custom_meshtastic_tags = Elecrow
custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 16MB
extends = crowpanel_small_esp32s3_base
build_flags =
${crowpanel_small_esp32s3_base.build_flags}
@@ -99,6 +110,17 @@ build_flags =
-D LGFX_TOUCH_ROTATION=0
[env:elecrow-adv-35-tft]
custom_meshtastic_hw_model = 97
custom_meshtastic_hw_model_slug = CROWPANEL
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Crowpanel Adv 3.5 TFT
custom_meshtastic_images = crowpanel_3_5.svg
custom_meshtastic_tags = Elecrow
custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 16MB
extends = crowpanel_small_esp32s3_base
board_level = pr
build_flags =
@@ -127,6 +149,17 @@ build_flags =
; 4.3, 5.0, 7.0 inch 800x480 IPS (V1)
[env:elecrow-adv1-43-50-70-tft]
custom_meshtastic_hw_model = 97
custom_meshtastic_hw_model_slug = CROWPANEL
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Crowpanel Adv 4.3/5.0/7.0 TFT
custom_meshtastic_images = crowpanel_5_0.svg, crowpanel_7_0.svg
custom_meshtastic_tags = Elecrow
custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 16MB
extends = crowpanel_large_esp32s3_base
build_flags =
${crowpanel_large_esp32s3_base.build_flags}

View File

@@ -3,8 +3,3 @@ extends = esp32_common
custom_esp32_kind = esp32s3
monitor_speed = 115200
build_flags =
${esp32_common.build_flags}
-DCONFIG_BT_NIMBLE_EXT_ADV=1
-DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2

View File

@@ -1,4 +1,14 @@
[env:heltec-v3]
[env:heltec-v3]
custom_meshtastic_hw_model = 43
custom_meshtastic_hw_model_slug = HELTEC_V3
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Heltec V3
custom_meshtastic_images = heltec-v3.svg, heltec-v3-case.svg
custom_meshtastic_tags = Heltec
custom_meshtastic_partition_scheme = 8MB
extends = esp32s3_base
board = heltec_wifi_lora_32_V3
board_level = pr

View File

@@ -10,6 +10,17 @@ build_flags =
[env:heltec-v4]
custom_meshtastic_hw_model = 110
custom_meshtastic_hw_model_slug = HELTEC_V4
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Heltec V4
custom_meshtastic_images = heltec_v4.svg
custom_meshtastic_tags = Heltec
custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 16MB
extends = heltec_v4_base
build_flags =
${heltec_v4_base.build_flags}

View File

@@ -29,10 +29,32 @@
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define USE_GC1109_PA // We have a GC1109 power amplifier+attenuator
#define LORA_PA_POWER 7 // power en
#define LORA_PA_EN 2
#define LORA_PA_TX_EN 46 // enable tx
// ---- GC1109 RF FRONT END CONFIGURATION ----
// The Heltec V4 uses a GC1109 FEM chip with integrated PA and LNA
// RF path: SX1262 -> GC1109 PA -> Pi attenuator -> Antenna
// Measured net TX gain (non-linear due to PA compression):
// +11dB at 0-15dBm input (e.g., 10dBm in -> 21dBm out)
// +10dB at 16-17dBm input
// +9dB at 18-19dBm input
// +7dB at 21dBm input (e.g., 21dBm in -> 28dBm out max)
// Control logic (from GC1109 datasheet):
// Shutdown: CSD=0, CTX=X, CPS=X
// Receive LNA: CSD=1, CTX=0, CPS=X (17dB gain, 2dB NF)
// Transmit bypass: CSD=1, CTX=1, CPS=0 (~1dB loss, no PA)
// Transmit PA: CSD=1, CTX=1, CPS=1 (full PA enabled)
// Pin mapping:
// CTX (pin 6) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH)
// CSD (pin 4) -> GPIO2: Chip enable (HIGH=on, LOW=shutdown)
// CPS (pin 5) -> GPIO46: PA mode select (HIGH=full PA, LOW=bypass)
// VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7
#define USE_GC1109_PA
#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 LDO power enable
#define LORA_PA_EN 2 // CSD - GC1109 chip enable (HIGH=on)
#define LORA_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass)
// GC1109 FEM: TX/RX path switching is handled by DIO2 -> CTX pin (via SX126X_DIO2_AS_RF_SWITCH)
// GPIO46 is CPS (PA mode), not TX control - setTransmitEnable() handles it in SX126xInterface.cpp
// Do NOT use SX126X_TXEN/RXEN as that would cause double-control of GPIO46
#if HAS_TFT
#define USE_TFTDISPLAY 1

View File

@@ -1,4 +1,15 @@
[env:heltec-vision-master-e213]
custom_meshtastic_hw_model = 67
custom_meshtastic_hw_model_slug = HELTEC_VISION_MASTER_E213
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Heltec Vision Master E213
custom_meshtastic_images = heltec-vision-master-e213.svg
custom_meshtastic_tags = Heltec
custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 8MB
extends = esp32s3_base
board = heltec_vision_master_e213
board_build.partitions = default_8MB.csv

View File

@@ -1,5 +1,16 @@
; Using the original screen class
[env:heltec-vision-master-e290]
custom_meshtastic_hw_model = 68
custom_meshtastic_hw_model_slug = HELTEC_VISION_MASTER_E290
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Heltec Vision Master E290
custom_meshtastic_images = heltec-vision-master-e290.svg
custom_meshtastic_tags = Heltec
custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 8MB
extends = esp32s3_base
board = heltec_vision_master_e290
board_build.partitions = default_8MB.csv

View File

@@ -1,4 +1,15 @@
[env:heltec-vision-master-t190]
custom_meshtastic_hw_model = 66
custom_meshtastic_hw_model_slug = HELTEC_VISION_MASTER_T190
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Heltec Vision Master T190
custom_meshtastic_images = heltec-vision-master-t190.svg
custom_meshtastic_tags = Heltec
custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 8MB
extends = esp32s3_base
board = heltec_vision_master_t190
board_build.partitions = default_8MB.csv

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