Compare commits

...

243 Commits

Author SHA1 Message Date
Ben Meadors
f8489773a8 Update src/platform/portduino/ch341a_i2c.c
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-07 17:05:04 +11:00
Ben Meadors
8a31276ae7 Update src/platform/portduino/ch341a_i2c.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-07 17:05:04 +11:00
Jonathan Bennett
4e23fac167 Move includes to header 2025-10-07 17:05:04 +11:00
Jonathan Bennett
c7dd670e61 Add scaffolding to read EEPROM data from ch341 2025-10-07 17:05:04 +11:00
Jonathan Bennett
b696e083f3 Log antispam (#8241)
* less power spam

* Don't warn about the first 4 GPS checksum failures
2025-10-07 16:37:04 +11:00
Austin
fc1737c949 Actions: Simplify matrices, cleanup build_one_* (#8218) 2025-10-07 11:58:00 +11:00
Jonathan Bennett
735784e6e4 Run Integration test in simulator mode (#8232) 2025-10-06 13:00:44 -05:00
Dmitry Dubinin
29f4d99bf6 Add Adaptive Polling Intervals to WebServer (#7864)
* feat: add adaptive polling intervals to WebServer

Replace fixed 5ms polling with adaptive intervals based on HTTP activity:
- 50ms during active periods (first 5 seconds after request)
- 200ms during medium activity (5-30 seconds)
- 1000ms during idle periods (30+ seconds)

Reduces CPU usage significantly during idle periods while maintaining
responsiveness when handling HTTP requests.

* Fix integer overflow and magic numbers in WebServer

- Handle millis() overflow in getAdaptiveInterval()
- Replace magic numbers with named constants
- Improve code readability and maintainability
2025-10-06 05:52:40 -05:00
Ben Meadors
18ca9e80d5 Merge pull request #8219 from GUVWAF/nextHopTrace
Update next-hops based on traceroute result
2025-10-05 17:59:37 -05:00
GUVWAF
5c2997ef53 Print only one byte 2025-10-05 17:03:52 +02:00
GUVWAF
c147ce9a85 Update next-hops based on traceroute result 2025-10-05 16:58:42 +02:00
Ben Meadors
27f316b931 Merge pull request #8216 from GUVWAF/reprocessPacket
Reprocess repeated packets and deduplicate logic
2025-10-05 08:58:13 -05:00
GUVWAF
f7cf5e6b0a Change to "rebroadcast" 2025-10-05 15:56:45 +02:00
GUVWAF
7c373b76c4 Reprocess repeated packets also 2025-10-05 14:04:35 +02:00
GUVWAF
de6a02756d De-duplicate handling upgraded packet and rebroadcasting logic 2025-10-05 14:03:55 +02:00
Jonathan Bennett
7c4367cddc Cppckeck suppress bogus error 2025-10-04 16:54:19 -05:00
Jonathan Bennett
6022b749ba Don't forget to break! 2025-10-04 16:54:19 -05:00
Jonathan Bennett
cbd30f95f3 Portduino: Only short-circuit hardware support when forcing sim mode 2025-10-04 16:54:19 -05:00
Jonathan Bennett
9ded6a5215 Pull in panel_sdl directly and drop native-sdl target 2025-10-04 16:54:19 -05:00
Ben Meadors
1e4bcb04d5 Merge remote-tracking branch 'origin/master' into develop 2025-10-04 08:24:43 -05:00
Ben Meadors
c4dff21e5b Develop -> Master (#8209)
* Create channel-mute toggle function

* Added mute state to channel settings

* Create node-mute toggle functions

* Added mute state to nodedb entries

* Rebase protos

* Decouple protobuf changes

* Disable bell-invoked ext notifs for muted nodes

* Clearly dilineate module mute from sender or channel mute

* Disable bell-invoked ext notifs for muted channels

* Trunk fmt

* Disable message-invoked ext notifs for muted channels and nodes

* Disable on-screen 'new message' popup for muted nodes and channels

* Don't mute alerts

* Make use of pre-existing channel_settings.module_settings.is_client_muted setting

* Revert previous commit - this needs it's own proto

* Regen protos

* T-Lora Pager: Interrupt based rotary encoder

* T-Lora Pager: Fix amplifier fuzzing/popping

* Fix build for other variants

* Fix - reference actual channel when changing settings

* Update protos

* Refactor ref syntax

* Fix defines

* Update comments and remove unused function

* Regen protos

* Regen protobuffs again

* Fix build failure in ci, add missing argument

* Format

* InputPollable: System for polling after interrupts

* T-Lora Pager: Use InputPollable for RotaryEncoderImpl

* Rename RotaryEncoderImpl to TLoraPagerRotaryEncoder

* Revert "Rename RotaryEncoderImpl to TLoraPagerRotaryEncoder"

This reverts commit a76cc88dc2.

* Revert unnecessary ifdefs

* Regen protos

* Use latest protos

* Regen protos for latest changes

* Decouple node-mute from channel-mute

* Regen protos

* Fix desktop build

* More flexible InputPollable paradigm

* Custom xPortInIsrContext() for nRF52/RP2xx0

* Use channel as specified in the received packet

* Use channel as specified in the received packet for OLED screen notifications

* add heltec tracker v2 board.

* Use common power amp definition for Heltec v4 and Heltec Tracker v2

* Set appropriate mqtt root upon lora region change

* Use user preferences root topic if present

* delete SX126X_MAX_POWER=11

* Assume previous root on topic change

* update mqtt root when region is changed via OLED menu handler

* Regen protos

* Removed magic numbers

* Add DIRECT_MSG_ONLY buzzer mode (#8158)

* Handle existing special case for M5STACK_UNITC6L for DIRECT_MSG_ONLY buzz mode

There already exists a special case for M5STACK_UNITC6L.
Modified it to adhere to new DIRECT_MSG_ONLY buzzer mode

* Add new buzzer mode DIRECT_MSG_ONLY to BuzzerModeMenu

* Disable notifications when buzzer mode is DIRECT_MSG_ONLY

* Change alert_message_buzzer in notification module in DIRECT_MSG_ONLY buzz mode

Better comments

* Fixed spelling in debug log

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

---------

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

* run trunk fmt

* Update variants/esp32s3/heltec_wireless_tracker_v2/variant.h

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

* Regen protos

* Pull latest protobufs

* Don't use IS_ONE_OF when loading Modules

* GAT562: Use PRIVATE_HW (fix build) (#8198)

* ESP32s2 doesn't implement HWCDC (#8199)

* Fix build script failure under certain conditions for devices that use UF2 binaries  (#8150)

* Validate CR and SF lora config (#8146)

* Validate CR and SF lora config

* No zero-bw

* Update src/modules/AdminModule.cpp

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

* Fix braces

---------

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

* Quote firmware paths given to uf2conv

---------

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

* Calculate airtime of transmitted and received packets separately (#8205)

* Correcting GPS PINs (#8087)

https://github.com/meshtastic/firmware/issues/8084

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

* Clear out user.id except for sending to phone (#8202)

* Null out user.id except for sending to phone

* Fix

* Update src/modules/NodeInfoModule.cpp

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

* Copilot garbage

* This is unnecessary, because we don't stored user.id on userlite

* Don't need this

* Fix warning

* Just alter the protobuf

* Alter protobuf doesn't do anything with the altered data, so let's re-encode it

* Check inputbroker before access

---------

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

* Add dropped packet count to LocalStats (#8207)

* Add dropped packet count to LocalStats
In case the transmit queue was full

* Trunked

---------

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

---------

Co-authored-by: ford-jones <fordnicholasjones@gmail.com>
Co-authored-by: WillyJL <me@willyjl.dev>
Co-authored-by: Ford Jones <107664313+ford-jones@users.noreply.github.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Quency-D <hj_zzns@163.com>
Co-authored-by: Quency-D <55523105+Quency-D@users.noreply.github.com>
Co-authored-by: nexpspace <380097+nexpspace@users.noreply.github.com>
Co-authored-by: nexpspace <4kosjdicx@mozmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Austin Lane <vidplace7@gmail.com>
Co-authored-by: Ken Piper <kealper@ivystone.net>
Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Co-authored-by: Szetya <szetya@gmail.com>
2025-10-04 08:14:41 -05:00
GUVWAF
888692a373 Add dropped packet count to LocalStats (#8207)
* Add dropped packet count to LocalStats
In case the transmit queue was full

* Trunked

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-10-04 08:13:58 -05:00
Ben Meadors
fe4fb085e6 Merge branch 'master' into develop 2025-10-04 06:43:54 -05:00
Ben Meadors
7c5e2bc95a Clear out user.id except for sending to phone (#8202)
* Null out user.id except for sending to phone

* Fix

* Update src/modules/NodeInfoModule.cpp

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

* Copilot garbage

* This is unnecessary, because we don't stored user.id on userlite

* Don't need this

* Fix warning

* Just alter the protobuf

* Alter protobuf doesn't do anything with the altered data, so let's re-encode it

* Check inputbroker before access

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-04 06:42:36 -05:00
github-actions[bot]
ed32650b9b Update protobufs (#8206)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-10-04 05:52:04 -05:00
Szetya
1b97cf57ad Correcting GPS PINs (#8087)
https://github.com/meshtastic/firmware/issues/8084

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-10-04 05:44:47 -05:00
GUVWAF
e8296914a5 Calculate airtime of transmitted and received packets separately (#8205) 2025-10-04 05:29:25 -05:00
Ken Piper
0e38fef5bf Fix build script failure under certain conditions for devices that use UF2 binaries (#8150)
* Validate CR and SF lora config (#8146)

* Validate CR and SF lora config

* No zero-bw

* Update src/modules/AdminModule.cpp

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

* Fix braces

---------

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

* Quote firmware paths given to uf2conv

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-03 18:53:18 -05:00
Austin
b7f6a2acb6 ESP32s2 doesn't implement HWCDC (#8199) 2025-10-03 18:52:51 -05:00
Austin
0c2283e19e GAT562: Use PRIVATE_HW (fix build) (#8198) 2025-10-03 16:48:21 -05:00
renovate[bot]
78d010fd29 Update actions/stale action to v10.1.0 (#8196)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-03 16:35:23 -05:00
renovate[bot]
037e56b1fd Update meshtastic/device-ui digest to 505ffad (#8195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-03 16:34:19 -05:00
Ben Meadors
c7208ca05b Merge pull request #8197 from vidplace7/modules-without-meshutils
Don't use IS_ONE_OF when loading Modules
2025-10-03 16:26:47 -05:00
Austin Lane
f72a4c50bd Don't use IS_ONE_OF when loading Modules 2025-10-03 17:14:00 -04:00
Ben Meadors
775595cb37 Merge pull request #8160 from Quency-D/dev-heltec-tracker-v2
add heltec tracker v2 board.
2025-10-03 08:21:15 -05:00
Ben Meadors
047600d088 Merge pull request #8166 from ford-jones/8139-root-topic
Update MQTT root on lora region change
2025-10-03 06:44:43 -05:00
Ben Meadors
560eb2c455 Merge branch 'develop' into 8139-root-topic 2025-10-03 06:37:36 -05:00
Ben Meadors
1be3820152 Merge pull request #8192 from meshtastic/master
Master backmerge
2025-10-03 06:35:03 -05:00
github-actions[bot]
da98622f59 Upgrade trunk (#8190)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-10-03 06:34:11 -05:00
github-actions[bot]
03baad2c11 Update protobufs (#8191)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-10-03 06:33:53 -05:00
Jonathan Bennett
0ddaf710e4 Add FACTORY_INSTALL option to do a filesystem reset on first boot (#8185)
* Add FACTORY_INSTALL option to do a filesystem reset on first boot

* Check for valid file handle before using

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-03 06:33:37 -05:00
ford-jones
50cfe7c705 Pull latest protobufs 2025-10-03 15:49:50 +13:00
ford-jones
76c1d69560 Regen protos 2025-10-03 15:28:08 +13:00
ford-jones
17863e96e2 Merge branch 'develop' of https://github.com/meshtastic/firmware into 8139-root-topic 2025-10-03 15:21:47 +13:00
Ben Meadors
c48a64e183 Merge pull request #7986 from WillyJL/fix/tlora-pager-rotary-amplifier
T-Lora Pager: Fully fix rotary encoder and speaker fuzzing/popping
2025-10-02 14:59:26 -05:00
Ben Meadors
e954591ca5 Merge branch 'develop' into fix/tlora-pager-rotary-amplifier 2025-10-02 14:40:37 -05:00
Jonathan Bennett
305f513834 Properly set Muzi Works R1 Neo HardwareModel 2025-10-02 10:40:32 -05:00
Ben Meadors
0860fee209 Merge branch 'develop' into dev-heltec-tracker-v2 2025-10-02 06:10:00 -05:00
Ben Meadors
878ac3ec84 Update variants/esp32s3/heltec_wireless_tracker_v2/variant.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-02 06:09:52 -05:00
Ben Meadors
a62e1cfa3c Merge pull request #7957 from ford-jones/7943-mute-target
Mute: channels
2025-10-02 05:41:16 -05:00
Ben Meadors
ca02808c5d Merge pull request #8184 from meshtastic/master
Master backmerge
2025-10-02 05:40:15 -05:00
github-actions[bot]
b978c6c86c Upgrade trunk (#8183)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-10-02 05:15:36 -05:00
ford-jones
51ad9d0244 run trunk fmt 2025-10-02 17:02:47 +13:00
Jonathan Bennett
76d4807130 Add support for the manually_verified bool in SharedContact (#8180) 2025-10-01 21:07:30 -05:00
Ford Jones
5ec09783c5 Merge branch 'develop' into 7943-mute-target 2025-10-02 14:28:03 +13:00
Ben Meadors
2eb0fcbcaf Merge branch 'develop' into 8139-root-topic 2025-10-01 19:40:41 -05:00
nexpspace
9bb7bb467b Add DIRECT_MSG_ONLY buzzer mode (#8158)
* Handle existing special case for M5STACK_UNITC6L for DIRECT_MSG_ONLY buzz mode

There already exists a special case for M5STACK_UNITC6L.
Modified it to adhere to new DIRECT_MSG_ONLY buzzer mode

* Add new buzzer mode DIRECT_MSG_ONLY to BuzzerModeMenu

* Disable notifications when buzzer mode is DIRECT_MSG_ONLY

* Change alert_message_buzzer in notification module in DIRECT_MSG_ONLY buzz mode

Better comments

* Fixed spelling in debug log

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

---------

Co-authored-by: nexpspace <4kosjdicx@mozmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-10-01 19:36:17 -05:00
Ben Meadors
de67714248 Merge branch 'develop' into 7943-mute-target 2025-10-01 19:31:40 -05:00
ford-jones
e0cf9130be Merge branch '8139-root-topic' of https://github.com/ford-jones/firmware into 8139-root-topic 2025-10-02 10:24:55 +13:00
ford-jones
f82667d71e Removed magic numbers 2025-10-02 10:24:32 +13:00
Ben Meadors
1d283523f2 Merge branch 'develop' into 8139-root-topic 2025-10-01 15:33:19 -05:00
github-actions[bot]
ec28c383af Upgrade trunk (#8159)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-10-01 15:32:25 -05:00
github-actions[bot]
641a2fc63d Update protobufs (#8178)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-10-01 15:32:06 -05:00
Mike Robbins
f7469159cf Reliable ACKs for DMs (#8165)
* RoutingModule::sendAckNak takes ackWantsAck arg to set want_ack on the ACK itself

* Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)

* Update ReliableRouter::sniffReceived to use ReliableRouter::shouldSuccessAckWithWantAck

* Use isFromUs

* Update MockRoutingModule::sendAckNak to include ackWantsAck argument (currently ignored)

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-10-01 15:31:53 -05:00
Ben Meadors
af83670376 Merge pull request #8179 from meshtastic/develop
Develop -> master
2025-10-01 15:30:57 -05:00
github-actions[bot]
849bbad279 Automated version bumps (#8177)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-10-01 15:13:28 -05:00
rcarteraz
b28d095096 missed t-rexes 2025-10-01 13:32:44 -05:00
rcarteraz
17afdb9ccf no more t-rex 2025-10-01 13:32:44 -05:00
Jonathan Bennett
d5164b4fbf Check the BUILD_EPOCH if defined 2025-10-01 13:32:44 -05:00
Jonathan Bennett
ad44940732 Put the GPIO in the right state for wake from sleep 2025-10-01 13:32:44 -05:00
Jonathan Bennett
8b466b1db3 T-rex comment cleanup 2025-10-01 13:32:44 -05:00
Jonathan Bennett
f9937967fa Add HardwareModel and correct directories 2025-10-01 13:32:44 -05:00
Jonathan Bennett
4fd568f384 Initial support for T-Rex 2025-10-01 13:32:44 -05:00
ford-jones
dae9b1c024 Regen protos 2025-10-01 17:58:14 +13:00
Ford Jones
a8a6644192 Merge branch 'develop' into 7943-mute-target 2025-10-01 17:56:54 +13:00
ford-jones
34a595b88e update mqtt root when region is changed via OLED menu handler 2025-10-01 16:14:21 +13:00
ford-jones
e32ce3fafe Merge branch '8139-root-topic' of https://github.com/ford-jones/firmware into 8139-root-topic 2025-10-01 11:14:59 +13:00
ford-jones
69c61f8247 Assume previous root on topic change 2025-10-01 11:14:27 +13:00
github-actions[bot]
b08e4efb78 Update protobufs (#8172)
Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com>
2025-09-30 13:34:40 -05:00
Ben Meadors
ee6857511a Fix Heltec V3 missed button presses (#8167) 2025-09-30 08:05:00 -05:00
Ben Meadors
9df4d57168 Merge branch 'develop' into 8139-root-topic 2025-09-30 05:52:28 -05:00
Quency-D
500e7920ae delete SX126X_MAX_POWER=11 2025-09-30 14:06:46 +08:00
ford-jones
ee8fa9f328 Use user preferences root topic if present 2025-09-30 18:04:42 +13:00
ford-jones
02efef3aaf Set appropriate mqtt root upon lora region change 2025-09-30 16:36:52 +13:00
Tom Fifield
0f6131d2c8 Use common power amp definition for Heltec v4 and Heltec Tracker v2 2025-09-30 08:30:18 +10:00
Quency-D
8d323a1cf1 add heltec tracker v2 board. 2025-09-30 08:20:48 +10:00
Tom Fifield
a3e6f16378 Introduce non-linear TX_GAIN_LORA (#8107)
* Introduce non-linear TX_GAIN_LORA

Previously, our TX_GAIN_LORA setting was a single number, intended
to represent the signal gain going through a power amp (plus or minus
antenna, attenuator, and other parts of the RF chain).

It turns out the relationship between the input power (i.e. from an SX1262)
and total output power is often non-linear. While we fudged a 1dBm difference
here and there with existing chips, the Heltec v4 has a 5dBm difference in gain
depending on which end of the input power (and frequency) you are at.

To allow people to run their Heltec v4 at max power when legal, and future
proof our code, this patch introduced an optional array-based TX_GAIN_LORA.

Define NUM_PA_POINTS and set TX_GAIN_LORA to gain values for a given input
power in 1dBm increments, and all will work.

For linear systems, just continue to define TX_GAIN_LORA as a number.

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

* Remove temporary power limit on heltec v4

* Add function RadioLibInterface::checkOutputPower

* Ensure SX126x reaches minimum supported power.

* Keep it simple, instead.
2025-09-30 08:20:39 +10:00
Ford Jones
85fe7d26ed Merge branch 'develop' into 7943-mute-target 2025-09-29 11:10:46 +13:00
Clive Blackledge
a1c658a467 Bug / Send upgraded (duplicate) packets to phone if the queue removal failed. (#8148)
* Add seenRecently = true if wasUpgraded is true but unable to remove from queue (i.e. already sent/processed).

* Consistent comment between FloodingRouter and HopRouter
2025-09-28 16:43:51 -05:00
Clive Blackledge
777e11bad9 Bug / Send upgraded (duplicate) packets to phone if the queue removal failed. (#8148)
* Add seenRecently = true if wasUpgraded is true but unable to remove from queue (i.e. already sent/processed).

* Consistent comment between FloodingRouter and HopRouter
2025-09-28 16:42:51 -05:00
Ben Meadors
a15d654767 Finish deprecating the Repeater role behavior (#8144)
* Finish deprecating the Repeater role behavior

* Validate

* Fixed bad if/else block

* Get your crap together!
2025-09-28 15:30:53 -05:00
Ben Meadors
033fc0c8f3 Validate CR and SF lora config (#8146)
* Validate CR and SF lora config

* No zero-bw

* Update src/modules/AdminModule.cpp

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

* Fix braces

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-28 13:13:07 -05:00
Ben Meadors
7633ddcfd1 Merge remote-tracking branch 'origin/master' into develop 2025-09-28 07:43:38 -05:00
github-actions[bot]
8717c60f13 Update protobufs (#8142)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-09-28 07:35:56 -05:00
Jason P
067939ca24 Correct altitudeLine getting clobbered in the great merge (#8138)
* Correct altitudeLine getting clobbered in the great merge

* Fix variable usage in altitude calculation
2025-09-28 06:11:01 -05:00
ford-jones
abc011aeb9 Use channel as specified in the received packet for OLED screen notifications 2025-09-28 16:26:45 +13:00
ford-jones
a4a6ee1df4 Merge branch '7943-mute-target' of https://github.com/ford-jones/firmware into 7943-mute-target 2025-09-28 13:21:56 +13:00
ford-jones
6448f069f8 Use channel as specified in the received packet 2025-09-28 13:18:21 +13:00
ford-jones
f6a28e15d2 Pull latest, regen protos 2025-09-28 11:38:36 +13:00
Ben Meadors
2b60bae61c Merge remote-tracking branch 'origin/develop' 2025-09-27 09:08:59 -05:00
dfsx1
bc516ebbac Remove memcpy (#8079)
Obsolete since #7652 returns false for mismatching keys

Co-authored-by: dfsx1 <dfsx1@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-09-27 08:33:07 -05:00
Ben Meadors
045176789e Fix int comparison and client_base base should really not be on this list 2025-09-27 08:32:43 -05:00
Ben Meadors
667b7c50e2 Merge pull request #8136 from plashchynski/fix_no_gps_double_message
UIRenderer: display "No GPS present" only on the first line to avoid duplication
2025-09-27 08:24:00 -05:00
Ben Meadors
9b1a118103 Merge branch 'develop' into 7943-mute-target 2025-09-27 08:22:14 -05:00
Ben Meadors
64c268f055 Merge branch 'develop' into fix_no_gps_double_message 2025-09-27 08:11:00 -05:00
Ben Meadors
806bf6ce2c Merge pull request #7703 from ford-jones/clear-rangetest-results
Range-test: Clean on reboot
2025-09-27 08:08:31 -05:00
Ford Jones
7eb0109e33 Merge branch 'develop' into clear-rangetest-results 2025-09-28 01:02:44 +12:00
Ford Jones
4dec912a39 Merge branch 'develop' into 7943-mute-target 2025-09-28 01:01:48 +12:00
Ben Meadors
73147c4028 Merge pull request #8110 from meshtastic/develop
Develop --> Master
2025-09-27 08:00:52 -05:00
Dzmitry Plashchynski
e8627b2d01 UIRenderer: display "No GPS present" only on the first line to avoid duplication 2025-09-27 15:56:52 +03:00
Ben Meadors
ab00e991f6 Revert cross-preset default-key bridging with UDP and disable UDP by default (#8130)
* Revert cross-preset UDP bridging

* Don't enable UDP by default
2025-09-27 07:09:24 -05:00
WillyJL
a2d86454d3 I2S: Fix silent RTTTL regression (#8129) 2025-09-27 15:07:38 +10:00
Jonathan Bennett
bc3db1b5c1 Properly output the TCXO Voltage in yaml (#8128) 2025-09-26 18:23:09 -05:00
github-actions[bot]
2f1198ddf3 Upgrade trunk (#8118)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-09-26 11:17:38 -05:00
Jason P
0624059683 Saving changes are required (#8122) 2025-09-26 11:17:15 -05:00
Ford Jones
52ee655fd2 Merge branch 'develop' into 7943-mute-target 2025-09-26 13:06:50 +12:00
Jason P
9980c56d81 Correct Inverted Mute Icon on Clock Display (#8111) 2025-09-26 08:48:34 +10:00
Ben Meadors
fc9f1ac056 Merge branch 'master' into develop 2025-09-25 17:09:47 -05:00
Ben Meadors
c65dbe490e Merge pull request #8101 from Links2004/reduce_cpu_load
reduce cpu load by optimizing OSThread runOnce calls
2025-09-25 16:29:42 -05:00
Ben Meadors
44636cc9f5 Merge pull request #8053 from GUVWAF/assymRelay
Make sure next-hop is only set when they received us directly
2025-09-25 13:54:22 -05:00
GUVWAF
aa876ba42f Merge branch 'develop' into assymRelay 2025-09-25 20:01:35 +02:00
GUVWAF
12c3ddf457 Resolve comments 2025-09-25 19:59:38 +02:00
Ben Meadors
d9f0590f8e Merge branch 'develop' into reduce_cpu_load 2025-09-25 11:59:22 -05:00
Ben Meadors
191d20ed04 Merge pull request #7982 from meshtastic/develop
Test develop --> master
2025-09-25 08:34:07 -05:00
Ford Jones
79bc286b35 Merge branch 'develop' into 7943-mute-target 2025-09-25 22:52:44 +12:00
Ford Jones
8d9fda38d6 Merge branch 'develop' into clear-rangetest-results 2025-09-25 22:47:16 +12:00
Erayd
3c25652cdf If a packet is heard multiple times, rebroadcast using the highest hop limit (#5534)
* If a packet is heard multiple times, rebroadcast using the highest hop limit

Sometimes a packet will be in the TX queue waiting to be transmitted,
when it is overheard being rebroadcast by another node, with a higher
hop limit remaining. When this occurs, modify the pending packet in
the TX queue to avoid unnecessarily wasting hops.

* Reprocess instead of modifying queued packet

In order to ensure that the traceroute module works correctly, rather
than modifying the hop limnit of the existing queued version of the
packet, simply drop it ifrom the queue and reprocess the version of the
packet with the superior hop limit.

* Update protobufs submodule

* Merge upstream/develop into overheard-hoptimisation branch

Resolved conflicts in:
- src/mesh/FloodingRouter.cpp: Integrated hop limit optimization with refactored duplicate handling
- src/mesh/MeshPacketQueue.h: Kept both hop_limit_lt parameter and new find() method

* Improve method naming and code clarity

- Rename findPacket() to getPacketFromQueue() for better clarity
- Make code DRY by having find() use getPacketFromQueue() internally
- Resolves method overloading conflict with clearer naming

* If a packet is heard multiple times, rebroadcast using the highest hop limit

Sometimes a packet will be in the TX queue waiting to be transmitted,
when it is overheard being rebroadcast by another node, with a higher
hop limit remaining. When this occurs, modify the pending packet in
the TX queue to avoid unnecessarily wasting hops.

* Improve router role checking using IS_ONE_OF macro

- Replace multiple individual role checks with cleaner IS_ONE_OF macro
- Add CLIENT_BASE support as suggested in PR #7992
- Include MeshTypes.h for IS_ONE_OF macro
- Makes code more maintainable and consistent with other parts of codebase

* Apply IS_ONE_OF improvement to NextHopRouter.cpp

- Replace multiple individual role checks with cleaner IS_ONE_OF macro
- Add CLIENT_BASE support for consistency
- Include MeshTypes.h for IS_ONE_OF macro
- Matches the pattern used in FloodingRouter.cpp

* Create and apply IS_ROUTER_ROLE() macro across codebase

- Add IS_ROUTER_ROLE() macro to meshUtils.h for consistent router role checking
- Update FloodingRouter.cpp to use macro in multiple locations
- Update NextHopRouter.cpp to use macro
- Include CLIENT_BASE role support

* Core Changes:
- Add hop_limit field to PacketRecord (17B→20B due to alignment)
- Extend wasSeenRecently() with wasUpgraded parameter
- Enable router optimization without duplicate app delivery
- Handle ROUTER_LATE delayed transmission properly

Technical Details:
- Memory overhead: ~4000 bytes for 1000 records
- Prevents duplicate message delivery while enabling routing optimization
- Maintains protocol integrity for ACK/NAK handling
- Supports upgrade from hop_limit=0 to hop_limit>0 scenarios

* Delete files accdentally added for merge

* Trunk formatting

* Packets are supposed to be unsigned. Thankfully, it's only a log message.

* Upgrade all packets, not just 0 hop packets.

* Not just unsigned, but hex. Updating packet log IDs.

* Fixed order of operations issue that prevented packetrs from being removed from the queue

* Fixing some bugs after testing. Only storing the maximum hop value in PacketRecord which makes sense. Also, updating messaging to make more sense in the logs.

* Fixed flow logic about how to handle re-inserting duplicate packets. Removed IS_ROUTER_ROLE macro and replaced it with better isRebroadcaster().

* Add logic to re-run modules, but avoid re-sending to phone.

* Refactor how to process the new packet with hops. Only update nodeDB and traceRouteModule.

* - Apply changes to both FloodingRouter and NextHopRouter classes to make packets mutable for traceroute
- MESHTASTIC_EXCLUDE_TRACEROUTE guard for when we don't want traceroute

* Allow MeshPacket to be modified in-place in processUpgradePacket

* let's not make a copy where a copy is unncessary.

---------

Co-authored-by: Clive Blackledge <clive@ansible.org>
Co-authored-by: Clive Blackledge <git@ansible.org>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-09-25 05:44:49 -05:00
Ben Meadors
9b3d76967b Merge branch 'develop' into assymRelay 2025-09-25 05:18:56 -05:00
Clive Blackledge
fd5ca8b73c Feat/0-cost hops for favorite routers (#7992)
* feat: implement router hop preservation for router-to-router communication

- Preserve hop_limit when both local device and previous relay are routers/CLIENT_BASE
- Only preserve hops for favorite routers to prevent abuse
- Apply to both FloodingRouter and NextHopRouter
- Update hop counting logic in MeshService for router-to-router communication

This allows routers to communicate over longer distances without
consuming hop limits, improving mesh network efficiency for
infrastructure nodes.

* chore: update protobufs submodule to latest

* Optimized to check friend list first before nodedb.

* Reverting unintended changes

* revert: remove protobufs submodule update

This reverts the protobufs submodule back to a84657c22 to remove
unintended changes from this branch.

* Slight rewrite to remove flawed NO_RELAY_NODE logic and added logic to add isFirstHop. If isFirstHop, always decrease hop_limit to avoid retry logic.

* DRY code. Remove NodeInfo logic that was left over.

* Trunk formatting
2025-09-25 05:17:51 -05:00
Chloe Bethel
18058ef507 Fix 2.4GHz reconfiguration on LR11xx (#8102)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-09-25 04:50:56 -05:00
Ben Meadors
d9ba0633f6 Merge branch 'develop' into clear-rangetest-results 2025-09-25 04:49:44 -05:00
Ben Meadors
68fc931518 Merge branch 'master' into develop 2025-09-25 04:48:08 -05:00
github-actions[bot]
0ad6b813fc Upgrade trunk (#8105)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-09-25 04:45:14 -05:00
Ben Meadors
d41fb7bcb5 Merge branch 'develop' into reduce_cpu_load 2025-09-25 04:43:56 -05:00
Ford Jones
fef4a2987d Merge branch 'develop' into clear-rangetest-results 2025-09-25 10:42:20 +12:00
Ford Jones
1fc8d54d4c Merge branch 'develop' into 7943-mute-target 2025-09-25 10:42:10 +12:00
WillyJL
47a82bdb98 Fix duplicated lines from merge (#8104) 2025-09-24 16:36:14 -05:00
Ben Meadors
8ed6514771 Merge branch 'develop' into assymRelay 2025-09-24 15:17:40 -05:00
Links2004
17ecd69416 onReceive does only exist for HardwareSerial not for USB CDC serial but we can at least check for USB connection in a longer interval 2025-09-24 22:09:32 +02:00
Links2004
bb6f19dddf the BluetoothPhoneAPI runOnce is triggerd by events any way no need to loop 2025-09-24 17:14:22 +02:00
Links2004
2fdc0d0928 save CPU cycles in ExternalNotificationModule
e.g. no need for a 25ms loop when we only blink a LED at 1sec
2025-09-24 17:14:22 +02:00
Links2004
85cdcad194 only run the ButtonThread if a button is pressed 2025-09-24 17:14:22 +02:00
Links2004
0b4a28866b add optional debug logging to see which OSThread / loops have what delays 2025-09-24 17:14:22 +02:00
Links2004
91e2e3f0e8 remove OSThread from BuzzerFeedbackThread 2025-09-24 17:14:22 +02:00
Links2004
14e64d6b9e move SerialConsole to event based trigger 2025-09-24 17:14:14 +02:00
Ford Jones
58602d59bd Merge branch 'develop' into 7943-mute-target 2025-09-25 00:24:41 +12:00
Ford Jones
d461eb35fc Merge branch 'develop' into clear-rangetest-results 2025-09-24 23:59:46 +12:00
Ben Meadors
371313080b Merge branch 'master' into develop 2025-09-24 06:18:13 -05:00
Ben Meadors
db55d8a59d Trunk 2025-09-24 06:16:51 -05:00
Quency-D
949f881ae8 Add three expansion screens for heltec mesh solar. (#7995)
* Add three expansion screens for heltec mesh solar.

* delete whitespace

Update variants/nrf52840/heltec_mesh_solar/variant.h

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

* delete whitespace

Update variants/nrf52840/heltec_mesh_solar/platformio.ini

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-09-24 06:16:51 -05:00
renovate[bot]
ca3c45a2f3 Update Adafruit BusIO to v1.17.4 (#8098)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-24 06:15:00 -05:00
Quency-D
c33c368315 Add three expansion screens for heltec mesh solar. (#7995)
* Add three expansion screens for heltec mesh solar.

* delete whitespace

Update variants/nrf52840/heltec_mesh_solar/variant.h

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

* delete whitespace

Update variants/nrf52840/heltec_mesh_solar/platformio.ini

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-09-24 06:14:24 -05:00
github-actions[bot]
1835ff2d78 Upgrade trunk (#8094)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-09-24 06:05:36 -05:00
Ben Meadors
8e04f9f631 Merge branch 'master' into develop 2025-09-24 06:03:14 -05:00
github-actions[bot]
83be632a1a Automated version bumps (#8100)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-09-24 06:02:55 -05:00
github-actions[bot]
1ed7aad976 Automated version bumps (#8100)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-09-24 06:01:31 -05:00
WillyJL
edb5c0f88e Custom xPortInIsrContext() for nRF52/RP2xx0 2025-09-24 05:17:07 +02:00
WillyJL
060a129995 More flexible InputPollable paradigm 2025-09-24 03:13:32 +02:00
WillyJL
a1ca553bc0 Fix desktop build 2025-09-23 22:30:01 +02:00
WillyJL
189aec9fe3 Merge remote-tracking branch 'upstream/develop' into fix/tlora-pager-rotary-amplifier 2025-09-23 19:44:00 +02:00
Ben Meadors
94d4bdf05c Revert "Fix build errors (#8067)"
This reverts commit d998f70b56.
2025-09-23 08:57:04 -05:00
Ben Meadors
1968a009dd Clear lasttoradio on BLE disconnect (#8095)
* On disconnect, clear the lastToRadio buffer

* Move it, bucko!
2025-09-23 07:31:25 -05:00
Ben Meadors
8e608e8186 Heltec V4 is 16mb 2025-09-23 06:04:47 -05:00
Jason P
d998f70b56 Fix build errors (#8067) 2025-09-23 05:39:57 -05:00
Ben Meadors
f55db903b2 Merge branch 'master' into develop 2025-09-23 05:38:52 -05:00
Jonathan Bennett
91efaba389 Remove line from BLE pin screen, to make pin readible on tiny screens 2025-09-22 21:59:00 -05:00
Jonathan Bennett
a8c66547cc Also pull a deviceID from esp32c6 devices (#8092) 2025-09-22 21:46:57 -05:00
Ben Meadors
f77ca2533b Try-fix: Unstick that PhoneAPI state (#8091)
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-09-22 21:46:35 -05:00
Jonathan Bennett
07b58a82d5 tlora-pager wake on button, and kb backlight toggling (#8090) 2025-09-22 21:06:23 -05:00
Ben Meadors
e1485b530f Handle ext. notification module things even if not enabled (#8089) 2025-09-22 19:59:05 -05:00
ford-jones
2fbfb19304 Regen protos 2025-09-23 12:40:48 +12:00
ford-jones
e7840122e8 Decouple node-mute from channel-mute 2025-09-23 11:40:45 +12:00
Ford Jones
a4e09aa9da Merge branch 'develop' into clear-rangetest-results 2025-09-23 10:31:09 +12:00
Jonathan Bennett
db941bff3b portduino bump to fix gpiod bug (#8083)
An earlier portduino causes problems with initializing gpiod lines. This pulls in the fix.
2025-09-22 12:00:01 -05:00
github-actions[bot]
13e1f99c7e Upgrade trunk (#8078)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-09-22 05:58:47 -05:00
ford-jones
319cd6fa7b Regen protos for latest changes 2025-09-22 15:14:31 +12:00
ford-jones
0db2e40ee3 Use latest protos 2025-09-22 15:07:47 +12:00
ford-jones
59f9e2a00b Regen protos 2025-09-22 14:59:45 +12:00
Tom Fifield
97d0f3286e Merge branch 'develop' into 7943-mute-target 2025-09-22 12:28:31 +10:00
WillyJL
da4bc0f97c Merge remote-tracking branch 'upstream/develop' into fix/tlora-pager-rotary-amplifier 2025-09-21 21:12:06 +02:00
Ben Meadors
d09baddce5 Merge branch 'develop' into assymRelay 2025-09-21 06:27:20 -05:00
Ford Jones
c811e4c573 Merge branch 'develop' into 7943-mute-target 2025-09-21 13:36:16 +12:00
WillyJL
d558df8a3a Revert unnecessary ifdefs 2025-09-21 03:29:52 +02:00
WillyJL
4100ba83a3 Revert "Rename RotaryEncoderImpl to TLoraPagerRotaryEncoder"
This reverts commit a76cc88dc2.
2025-09-21 03:23:16 +02:00
WillyJL
a76cc88dc2 Rename RotaryEncoderImpl to TLoraPagerRotaryEncoder 2025-09-20 18:53:30 +02:00
ford-jones
3463006f73 Merge branch 'develop' of https://github.com/meshtastic/firmware into clear-rangetest-results 2025-09-21 00:26:19 +12:00
GUVWAF
1fc07607cb Make sure next-hop is only set when they received us directly 2025-09-20 13:03:46 +02:00
ford-jones
22b71a1e95 Pull latest changes from https://github.com/meshtastic/protobufs.git 2025-09-20 17:52:41 +12:00
ford-jones
2ccf91f443 Regen protos 2025-09-20 17:21:26 +12:00
ford-jones
58e4dcea61 Merge branch 'develop' of https://github.com/meshtastic/firmware into clear-rangetest-results 2025-09-20 14:31:05 +12:00
WillyJL
bfb03b422a Merge remote-tracking branch 'upstream/develop' into fix/tlora-pager-rotary-amplifier 2025-09-20 02:36:57 +02:00
WillyJL
54f9f7a591 T-Lora Pager: Use InputPollable for RotaryEncoderImpl 2025-09-19 22:05:18 +02:00
WillyJL
0e26702c46 InputPollable: System for polling after interrupts 2025-09-19 21:52:51 +02:00
ford-jones
901bcc24ee Reflect requirement of ESP32 hardware in rangetest logs 2025-09-19 22:17:03 +12:00
WillyJL
d427b477e3 Format 2025-09-17 02:07:24 +02:00
Ford Jones
c9cb2cfd94 Merge branch 'develop' into 7943-mute-target 2025-09-16 23:14:52 +12:00
ford-jones
43078a40eb Fix build failure in ci, add missing argument 2025-09-16 21:57:51 +12:00
ford-jones
4ac99c5df1 Regen protobuffs again 2025-09-16 19:26:22 +12:00
ford-jones
c9702fe4d0 Regen protos 2025-09-16 19:21:53 +12:00
ford-jones
e0f88be2d7 Merge branch 'develop' of https://github.com/meshtastic/firmware into 7943-mute-target 2025-09-16 19:16:44 +12:00
ford-jones
1c256ccfd7 Update comments and remove unused function 2025-09-16 15:43:13 +12:00
WillyJL
6c932d51ec Fix defines 2025-09-15 17:49:03 +02:00
ford-jones
f0b7aab030 Refactor ref syntax 2025-09-15 15:21:40 +12:00
ford-jones
5fca3a30ec Update protos 2025-09-15 15:13:25 +12:00
ford-jones
a76f591231 Fix - reference actual channel when changing settings 2025-09-15 15:08:02 +12:00
WillyJL
20f68929c8 Fix build for other variants 2025-09-14 20:17:24 +02:00
WillyJL
42e4759634 T-Lora Pager: Fix amplifier fuzzing/popping 2025-09-14 18:50:56 +02:00
WillyJL
3d86c99c25 T-Lora Pager: Interrupt based rotary encoder 2025-09-14 18:48:58 +02:00
ford-jones
bfadd9c866 Regen protos 2025-09-13 17:51:52 +12:00
ford-jones
f8d44f8f6c Revert previous commit - this needs it's own proto 2025-09-13 17:45:07 +12:00
ford-jones
ccff2769fe Make use of pre-existing channel_settings.module_settings.is_client_muted setting 2025-09-13 13:39:32 +12:00
ford-jones
e0890b2a13 Don't mute alerts 2025-09-12 23:01:42 +12:00
ford-jones
5579d87845 Disable on-screen 'new message' popup for muted nodes and channels 2025-09-12 19:52:34 +12:00
ford-jones
693181b2be Disable message-invoked ext notifs for muted channels and nodes 2025-09-12 15:45:10 +12:00
ford-jones
39c663f203 Merge branch 'develop' of https://github.com/meshtastic/firmware into 7943-mute-target 2025-09-12 14:23:01 +12:00
ford-jones
71f659cba6 Trunk fmt 2025-09-12 14:15:06 +12:00
ford-jones
4e879a7b26 Disable bell-invoked ext notifs for muted channels 2025-09-12 14:12:55 +12:00
ford-jones
8c9c00172c Clearly dilineate module mute from sender or channel mute 2025-09-12 14:12:22 +12:00
ford-jones
d5300a1141 Disable bell-invoked ext notifs for muted nodes 2025-09-12 13:54:52 +12:00
ford-jones
a31fdf01ce Decouple protobuf changes 2025-09-11 22:23:42 +12:00
ford-jones
1594421214 Rebase protos 2025-09-11 21:49:25 +12:00
ford-jones
02cb306bb1 Merge branch 'develop' into 7943-mute-target 2025-09-11 19:59:38 +12:00
ford-jones
6b7ad9c4e1 Added mute state to nodedb entries 2025-09-11 17:32:12 +12:00
ford-jones
fa1ccf4779 Create node-mute toggle functions 2025-09-11 17:30:59 +12:00
ford-jones
67ecb60bcd Added mute state to channel settings 2025-09-11 14:18:00 +12:00
ford-jones
9da92626e5 Create channel-mute toggle function 2025-09-11 14:16:48 +12:00
ford-jones
b75e8913e0 Fix: Compile latest protobufs 2025-09-09 13:14:20 +12:00
Ford Jones
87a1449f76 Merge branch 'develop' into clear-rangetest-results 2025-09-09 12:05:00 +12:00
ford-jones
c62f262f63 Trunk fmt 2025-09-03 13:38:39 +12:00
ford-jones
798040b5b8 Merge branch 'develop' of https://github.com/meshtastic/firmware into clear-rangetest-results 2025-09-03 13:35:50 +12:00
ford-jones
ba582d6ef4 Protobuf naming reflected in config-switch 2025-09-03 12:23:59 +12:00
ford-jones
bbf6f01d42 Resolve merge conflict 2025-09-03 12:10:17 +12:00
ford-jones
142abb2a4e Updated naming to match protobuf 2025-09-03 12:06:35 +12:00
ford-jones
4dfcd61d46 If specified, Clean out range test results on module init 2025-08-21 20:05:30 +10:00
ford-jones
9d560fe9e1 Enable protobufs to include rangetest deletion configuration 2025-08-21 20:05:30 +10:00
ford-jones
8e32d58077 Check filesystem mounted 2025-08-21 20:05:30 +10:00
ford-jones
7b24d31636 Use string constants in place of char* 2025-08-21 20:05:30 +10:00
ford-jones
35d9e68053 Enabled deletion of files created by the range-test module 2025-08-21 20:05:30 +10:00
ford-jones
caf2180075 If specified, Clean out range test results on module init 2025-08-21 19:28:52 +12:00
ford-jones
236d2b92dc Enable protobufs to include rangetest deletion configuration 2025-08-21 12:12:13 +12:00
ford-jones
e6a2df5b6d Check filesystem mounted 2025-08-21 12:01:45 +12:00
ford-jones
f6bb1977bc Use string constants in place of char* 2025-08-21 12:00:19 +12:00
ford-jones
9b0fbcf1d9 Enabled deletion of files created by the range-test module 2025-08-21 11:55:53 +12:00
141 changed files with 3460 additions and 1466 deletions

View File

@@ -19,6 +19,8 @@ jobs:
pio-build:
name: build-${{ inputs.platform }}
runs-on: ubuntu-24.04
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
- uses: actions/checkout@v5
with:
@@ -55,6 +57,7 @@ jobs:
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
id: upload
with:
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
overwrite: true

View File

@@ -3,6 +3,7 @@ name: Build One Arch
on:
workflow_dispatch:
inputs:
# trunk-ignore(checkov/CKV_GHA_7)
arch:
type: choice
options:
@@ -16,10 +17,13 @@ on:
- stm32
- native
permissions: read-all
env:
INPUT_ARCH: ${{ github.event.inputs.arch }}
jobs:
setup:
strategy:
fail-fast: false
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
@@ -31,23 +35,11 @@ jobs:
- name: Generate matrix
id: jsonStep
run: |
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra)
else
TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr)
fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra)
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT
outputs:
esp32: ${{ steps.jsonStep.outputs.esp32 }}
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
stm32: ${{ steps.jsonStep.outputs.stm32 }}
check: ${{ steps.jsonStep.outputs.check }}
selected_arch: ${{ steps.jsonStep.outputs.selected_arch }}
version:
runs-on: ubuntu-latest
@@ -64,101 +56,18 @@ jobs:
long: ${{ steps.version.outputs.long }}
deb: ${{ steps.version.outputs.deb }}
build-esp32:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}}
build:
if: ${{ github.event_name != 'workflow_dispatch' }}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
matrix:
build: ${{ fromJson(needs.setup.outputs.selected_arch) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32
build-esp32s3:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32s3
build-esp32c3:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c3
build-esp32c6:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c6
build-nrf52840:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: nrf52840
build-rp2040:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2040
build-rp2350:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2350
build-stm32:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }}
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: stm32
pio_env: ${{ matrix.build.board }}
platform: ${{ matrix.build.arch }}
build-debian-src:
if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
@@ -252,18 +161,7 @@ jobs:
- rp2350
- stm32
runs-on: ubuntu-latest
needs:
[
version,
build-esp32,
build-esp32s3,
build-esp32c3,
build-esp32c6,
build-nrf52840,
build-rp2040,
build-rp2350,
build-stm32,
]
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v5
@@ -332,169 +230,3 @@ jobs:
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}
release-artifacts:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
- version
- gather-artifacts
- build-debian-src
- package-pio-deps-native-tft
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Create release
uses: softprops/action-gh-release@v2
id: create_release
with:
draft: true
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
- name: Download source deb
uses: actions/download-artifact@v5
with:
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
merge-multiple: true
path: ./output/debian-src
- name: Download `native-tft` pio deps
uses: actions/download-artifact@v5
with:
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output/pio-deps-native-tft
- name: Zip Linux sources
working-directory: output
run: |
zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
# For diagnostics
- name: Display structure of downloaded files
run: ls -lR
- name: Add Linux sources to GtiHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-firmware:
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
- name: Display structure of downloaded files
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v5
with:
name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
merge-multiple: true
path: ./elfs
- name: Zip debug elfs
run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
# For diagnostics
- name: Display structure of downloaded files
run: ls -lR
- name: Add bins and debug elfs to GitHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-firmware:
runs-on: ubuntu-24.04
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-firmware, version]
env:
targets: |-
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: actions/download-artifact@v5
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Publish firmware to meshtastic.github.io
uses: peaceiris/actions-gh-pages@v4
env:
# On event/* branches, use the event name as the destination prefix
DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
with:
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
external_repository: meshtastic/meshtastic.github.io
publish_branch: master
publish_dir: ./publish
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
keep_files: true
user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com
commit_message: ${{ needs.version.outputs.long }}
enable_jekyll: true

View File

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

View File

@@ -27,19 +27,11 @@ on:
jobs:
setup:
if: github.repository == 'meshtastic/firmware'
strategy:
fail-fast: false
fail-fast: true
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- all
- check
runs-on: ubuntu-24.04
steps:
@@ -49,33 +41,22 @@ jobs:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Uncomment build epoch
shell: bash
run: |
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
- name: Generate matrix
id: jsonStep
run: |
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
else
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
echo "$TARGETS" >> $GITHUB_STEP_SUMMARY
outputs:
esp32: ${{ steps.jsonStep.outputs.esp32 }}
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
stm32: ${{ steps.jsonStep.outputs.stm32 }}
all: ${{ steps.jsonStep.outputs.all }}
check: ${{ steps.jsonStep.outputs.check }}
version:
if: github.repository == 'meshtastic/firmware'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
@@ -94,7 +75,8 @@ jobs:
needs: setup
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.check) }}
matrix:
check: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
@@ -103,96 +85,20 @@ jobs:
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Check ${{ matrix.board }}
run: bin/check-all.sh ${{ matrix.board }}
- name: Check ${{ matrix.check.board }}
run: bin/check-all.sh ${{ matrix.check.board }}
build-esp32:
build:
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
matrix:
build: ${{ fromJson(needs.setup.outputs.all) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32
build-esp32s3:
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32s3
build-esp32c3:
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c3
build-esp32c6:
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c6
build-nrf52840:
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: nrf52840
build-rp2040:
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2040
build-rp2350:
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2350
build-stm32:
needs: [setup, version]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: stm32
pio_env: ${{ matrix.build.board }}
platform: ${{ matrix.build.platform }}
build-debian-src:
if: github.repository == 'meshtastic/firmware'
@@ -203,7 +109,7 @@ jobs:
secrets: inherit
package-pio-deps-native-tft:
if: ${{ github.event_name == 'workflow_dispatch' }}
if: ${{ github.repository == 'meshtastic/firmware' && github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
@@ -288,18 +194,7 @@ jobs:
- rp2350
- stm32
runs-on: ubuntu-latest
needs:
[
version,
build-esp32,
build-esp32s3,
build-esp32c3,
build-esp32c6,
build-nrf52840,
build-rp2040,
build-rp2350,
build-stm32,
]
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v5

View File

@@ -7,23 +7,13 @@ on:
# Merge group is a special trigger that is used to trigger the workflow when a merge group is created.
merge_group:
env:
FAIL_FAST_PER_ARCH: true
jobs:
setup:
strategy:
fail-fast: true
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- all
- check
runs-on: ubuntu-24.04
steps:
@@ -39,19 +29,12 @@ jobs:
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
else
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
outputs:
esp32: ${{ steps.jsonStep.outputs.esp32 }}
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
stm32: ${{ steps.jsonStep.outputs.stm32 }}
all: ${{ steps.jsonStep.outputs.all }}
check: ${{ steps.jsonStep.outputs.check }}
version:
@@ -73,7 +56,8 @@ jobs:
needs: setup
strategy:
fail-fast: true
matrix: ${{ fromJson(needs.setup.outputs.check) }}
matrix:
check: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }}
@@ -82,96 +66,19 @@ jobs:
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Check ${{ matrix.board }}
run: bin/check-all.sh ${{ matrix.board }}
- name: Check ${{ matrix.check.board }}
run: bin/check-all.sh ${{ matrix.check.board }}
build-esp32:
build:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
matrix:
build: ${{ fromJson(needs.setup.outputs.all) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32
build-esp32s3:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32s3
build-esp32c3:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c3
build-esp32c6:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: esp32c6
build-nrf52840:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: nrf52840
build-rp2040:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2040
build-rp2350:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: rp2350
build-stm32:
needs: [setup, version]
strategy:
fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.board }}
platform: stm32
pio_env: ${{ matrix.build.board }}
platform: ${{ matrix.build.platform }}
build-debian-src:
if: github.repository == 'meshtastic/firmware'
@@ -260,18 +167,7 @@ jobs:
- rp2350
- stm32
runs-on: ubuntu-latest
needs:
[
version,
build-esp32,
build-esp32s3,
build-esp32c3,
build-esp32c6,
build-nrf52840,
build-rp2040,
build-rp2350,
build-stm32,
]
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v5

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: Stale PR+Issues
uses: actions/stale@v10.0.0
uses: actions/stale@v10.1.0
with:
days-before-stale: 45
exempt-issue-labels: pinned,3.0

View File

@@ -40,7 +40,7 @@ jobs:
- name: Integration test
run: |
.pio/build/coverage/program &
.pio/build/coverage/program -s &
PID=$!
timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
echo "Simulator started, launching python test..."

View File

@@ -8,25 +8,25 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.471
- renovate@41.115.6
- checkov@3.2.473
- renovate@41.132.5
- prettier@3.6.2
- trufflehog@3.90.6
- trufflehog@3.90.8
- yamllint@1.37.1
- bandit@1.8.6
- trivy@0.66.0
- trivy@0.67.0
- taplo@0.10.0
- ruff@0.13.0
- isort@6.0.1
- ruff@0.13.2
- isort@6.1.0
- markdownlint@0.45.0
- oxipng@9.1.5
- svgo@4.0.0
- actionlint@1.7.7
- flake8@7.3.0
- hadolint@2.13.1
- hadolint@2.14.0
- shfmt@3.6.0
- shellcheck@0.11.0
- black@25.1.0
- black@25.9.0
- git-diff-check
- gitleaks@8.28.0
- clang-format@16.0.3

View File

@@ -36,6 +36,7 @@ build_flags =
-DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
-DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
-DSERIAL_BUFFER_SIZE=4096
-DSERIAL_HAS_ON_RECEIVE
-DLIBPAX_ARDUINO
-DLIBPAX_WIFI
-DLIBPAX_BLE

View File

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

View File

@@ -1,28 +1,32 @@
#!/usr/bin/env python
#!/usr/bin/env python3
"""Generate the CI matrix."""
import argparse
import json
import sys
import random
import re
from platformio.project.config import ProjectConfig
options = sys.argv[1:]
parser = argparse.ArgumentParser(description="Generate the CI matrix")
parser.add_argument("platform", help="Platform to build for")
parser.add_argument(
"--level",
choices=["extra", "pr"],
nargs="*",
default=[],
help="Board level to build for (omit for full release boards)",
)
args = parser.parse_args()
outlist = []
if len(options) < 1:
print(json.dumps(outlist))
exit(1)
cfg = ProjectConfig.get_instance()
pio_envs = cfg.envs()
# Gather all PlatformIO environments for filtering later
all_envs = []
for pio_env in pio_envs:
env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags')
env_build_flags = cfg.get(f"env:{pio_env}", "build_flags")
env_platform = None
for flag in env_build_flags:
# Extract the platform from the build flags
@@ -37,36 +41,35 @@ for pio_env in pio_envs:
exit(1)
# Store env details as a dictionary, and add to 'all_envs' list
env = {
'name': pio_env,
'platform': env_platform,
'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None),
'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False))
"ci": {"board": pio_env, "platform": env_platform},
"board_level": cfg.get(f"env:{pio_env}", "board_level", default=None),
"board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)),
}
all_envs.append(env)
# Filter outputs based on options
# Check is mutually exclusive with other options (except 'pr')
if "check" in options:
if "check" in args.platform:
for env in all_envs:
if env['board_check']:
if "pr" in options:
if env['board_level'] == 'pr':
outlist.append(env['name'])
if env["board_check"]:
if "pr" in args.level:
if env["board_level"] == "pr":
outlist.append(env["ci"])
else:
outlist.append(env['name'])
outlist.append(env["ci"])
# Filter (non-check) builds by platform
else:
for env in all_envs:
if options[0] == env['platform']:
if args.platform == env["ci"]["platform"] or args.platform == "all":
# Always include board_level = 'pr'
if env['board_level'] == 'pr':
outlist.append(env['name'])
if env["board_level"] == "pr":
outlist.append(env["ci"])
# Include board_level = 'extra' when requested
elif "extra" in options and env['board_level'] == "extra":
outlist.append(env['name'])
elif "extra" in args.level and env["board_level"] == "extra":
outlist.append(env["ci"])
# If no board level is specified, include in release builds (not PR)
elif "pr" not in options and not env['board_level']:
outlist.append(env['name'])
elif "pr" not in args.level and not env["board_level"]:
outlist.append(env["ci"])
# Return as a JSON list
print(json.dumps(outlist))

View File

@@ -87,6 +87,12 @@
</screenshots>
<releases>
<release version="2.7.12" date="2025-10-01">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.12</url>
</release>
<release version="2.7.11" date="2025-09-24">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.11</url>
</release>
<release version="2.7.10" date="2025-09-18">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.10</url>
</release>

View File

@@ -86,7 +86,7 @@ if platform.name == "espressif32":
if platform.name == "nordicnrf52":
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex",
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2",
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py \"$BUILD_DIR/firmware.hex\" -c -f 0xADA52840 -o \"$BUILD_DIR/firmware.uf2\"",
"Generating UF2 file"))
Import("projenv")

View File

@@ -0,0 +1,37 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-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",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "heltec_wireless_tracker_v2"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "Heltec Wireless Tracker V2",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://heltec.org",
"vendor": "Heltec"
}

52
boards/r1-neo.json Normal file
View File

@@ -0,0 +1,52 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "Muzi R1 Neo",
"mcu": "nrf52840",
"variant": "r1-neo",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino", "freertos"],
"name": "WisCore RAK4631 Board",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://muzi.works/",
"vendor": "Muzi Works"
}

46
debian/changelog vendored
View File

@@ -1,49 +1,13 @@
meshtasticd (2.7.10.0) UNRELEASED; urgency=medium
meshtasticd (2.7.12.0) unstable; urgency=medium
[ Austin Lane ]
* Initial packaging
* Version 2.5.19
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ GitHub Actions ]
* Version 2.7.12
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ Ubuntu ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
[ ]
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Thu, 18 Sep 2025 22:11:37 +0000
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 01 Oct 2025 19:51:41 +0000

View File

@@ -56,6 +56,7 @@ build_flags = -Wno-missing-field-initializers
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
#-D OLED_PL=1
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
#-D DEBUG_LOOP_TIMING=1 ; uncomment to add main loop timing logs
monitor_speed = 115200
monitor_filters = direct
@@ -119,13 +120,13 @@ 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/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip
https://github.com/meshtastic/device-ui/archive/505ffadaa7a931df5dc8153229b719a07bbb028c.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
adafruit/Adafruit BusIO@1.17.3
adafruit/Adafruit BusIO@1.17.4
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library

View File

@@ -11,6 +11,11 @@
#include <AudioOutputI2S.h>
#include <ESP8266SAM.h>
#ifdef USE_XL9555
#include "ExtensionIOXL9555.hpp"
extern ExtensionIOXL9555 io;
#endif
#define AUDIO_THREAD_INTERVAL_MS 100
class AudioThread : public concurrency::OSThread
@@ -20,12 +25,16 @@ class AudioThread : public concurrency::OSThread
void beginRttl(const void *data, uint32_t len)
{
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
#endif
setCPUFast(true);
rtttlFile = new AudioFileSourcePROGMEM(data, len);
i2sRtttl = new AudioGeneratorRTTTL();
i2sRtttl->begin(rtttlFile, audioOut);
}
// Also handles actually playing the RTTTL, needs to be called in loop
bool isPlaying()
{
if (i2sRtttl != nullptr) {
@@ -45,6 +54,9 @@ class AudioThread : public concurrency::OSThread
rtttlFile = nullptr;
setCPUFast(false);
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, LOW);
#endif
}
void readAloud(const char *text)
@@ -55,10 +67,16 @@ class AudioThread : public concurrency::OSThread
i2sRtttl = nullptr;
}
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
#endif
ESP8266SAM *sam = new ESP8266SAM;
sam->Say(audioOut, text);
delete sam;
setCPUFast(false);
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, LOW);
#endif
}
protected:

View File

@@ -76,9 +76,6 @@ const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role
case meshtastic_Config_DeviceConfig_Role_ROUTER_LATE:
return "Router Late";
break;
case meshtastic_Config_DeviceConfig_Role_REPEATER:
return "Repeater";
break;
default:
return "Unknown";
break;

View File

@@ -828,8 +828,11 @@ void Power::readPowerStatus()
// Notify any status instances that are observing us
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(),
powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
if (millis() > lastLogTime + 50 * 1000) {
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
lastLogTime = millis();
}
newStatus.notifyObservers(&powerStatus2);
#ifdef DEBUG_HEAP
if (lastheap != memGet.getFreeHeap()) {

View File

@@ -6,6 +6,14 @@
#include "configuration.h"
#include "time.h"
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
#define IS_USB_SERIAL
#ifdef SERIAL_HAS_ON_RECEIVE
#undef SERIAL_HAS_ON_RECEIVE
#endif
#include "HWCDC.h"
#endif
#ifdef RP2040_SLOW_CLOCK
#define Port Serial2
#else
@@ -22,7 +30,12 @@ SerialConsole *console;
void consoleInit()
{
new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
auto sc = new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
#if defined(SERIAL_HAS_ON_RECEIVE)
// onReceive does only exist for HardwareSerial not for USB CDC serial
Port.onReceive([sc]() { sc->rxInt(); });
#endif
DEBUG_PORT.rpInit(); // Simply sets up semaphore
}
@@ -65,14 +78,21 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
int32_t SerialConsole::runOnce()
{
#ifdef HELTEC_MESH_SOLAR
//After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port
&& moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)
{
// After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
if (moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port &&
moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) {
return 250;
}
#endif
return runOncePart();
int32_t delay = runOncePart();
#if defined(SERIAL_HAS_ON_RECEIVE) || defined(CONFIG_IDF_TARGET_ESP32S2)
return Port.available() ? delay : INT32_MAX;
#elif defined(IS_USB_SERIAL)
return HWCDC::isPlugged() ? delay : (1000 * 20);
#else
return delay;
#endif
}
void SerialConsole::flush()
@@ -80,6 +100,18 @@ void SerialConsole::flush()
Port.flush();
}
// trigger tx of serial data
void SerialConsole::onNowHasData(uint32_t fromRadioNum)
{
setIntervalFromNow(0);
}
// trigger rx of serial data
void SerialConsole::rxInt()
{
setIntervalFromNow(0);
}
// For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages
bool SerialConsole::checkIsConnected()
{

View File

@@ -32,11 +32,14 @@ class SerialConsole : public StreamAPI, public RedirectablePrint, private concur
virtual int32_t runOnce() override;
void flush();
void rxInt();
protected:
/// Check the current underlying physical link to see if the client is currently connected
virtual bool checkIsConnected() override;
virtual void onNowHasData(uint32_t fromRadioNum) override;
/// Possibly switch to protobufs if we see a valid protobuf message
virtual void log_to_serial(const char *logLevel, const char *format, va_list arg);
};

View File

@@ -5,7 +5,7 @@
BuzzerFeedbackThread *buzzerFeedbackThread;
BuzzerFeedbackThread::BuzzerFeedbackThread() : OSThread("BuzzerFeedback")
BuzzerFeedbackThread::BuzzerFeedbackThread()
{
if (inputBroker)
inputObserver.observe(inputBroker);
@@ -15,14 +15,11 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
{
// Only provide feedback if buzzer is enabled for notifications
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY) {
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY ||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY) {
return 0; // Let other handlers process the event
}
// Track last event time for potential future use
lastEventTime = millis();
needsUpdate = true;
// Handle different input events with appropriate buzzer feedback
switch (event->inputEvent) {
case INPUT_BROKER_USER_PRESS:
@@ -61,15 +58,4 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
}
return 0; // Allow other handlers to process the event
}
int32_t BuzzerFeedbackThread::runOnce()
{
// This thread is primarily event-driven, but we can use runOnce
// for any periodic tasks if needed in the future
needsUpdate = false;
// Run every 100ms when active, less frequently when idle
return needsUpdate ? 100 : 1000;
}
}

View File

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

View File

@@ -90,7 +90,9 @@ void OSThread::run()
if (heap < newHeap)
LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
#endif
#ifdef DEBUG_LOOP_TIMING
LOG_DEBUG("====== Thread next run in: %d", newDelay);
#endif
runned();
if (newDelay >= 0)

View File

@@ -117,6 +117,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define SX126X_MAX_POWER 22
#endif
#ifdef USE_GC1109_PA
// Power Amps are often non-linear, so we can use an array of values for the power curve
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
#endif
// Default system gain to 0 if not defined
#ifndef TX_GAIN_LORA
#define TX_GAIN_LORA 0

View File

@@ -25,8 +25,8 @@ ScanI2C::FoundDevice ScanI2C::firstScreen() const
ScanI2C::FoundDevice ScanI2C::firstRTC() const
{
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563};
return firstOfOrNONE(2, types);
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563, RTC_RX8130CE};
return firstOfOrNONE(3, types);
}
ScanI2C::FoundDevice ScanI2C::firstKeyboard() const

View File

@@ -14,6 +14,7 @@ class ScanI2C
SCREEN_ST7567,
RTC_RV3028,
RTC_PCF8563,
RTC_RX8130CE,
CARDKB,
TDECKKB,
BBQ10KB,

View File

@@ -197,6 +197,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#ifdef PCF8563_RTC
SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563", (uint8_t)addr.address)
#endif
#ifdef RX8130CE_RTC
SCAN_SIMPLE_CASE(RX8130CE_RTC, RTC_RX8130CE, "RX8130CE", (uint8_t)addr.address)
#endif
case CARDKB_ADDR:
// Do we have the RAK14006 instead?

View File

@@ -1104,11 +1104,6 @@ int32_t GPS::runOnce()
publishUpdate();
}
// Repeaters have no need for GPS
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
return disable();
}
if (whileActive()) {
// if we have received valid NMEA claim we are connected
setConnected();
@@ -1594,8 +1589,12 @@ bool GPS::lookForLocation()
#ifndef TINYGPS_OPTION_NO_STATISTICS
if (reader.failedChecksum() > lastChecksumFailCount) {
LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
reader.failedChecksum());
// In a GPS_DEBUG build we want to log all of these. In production, we only care if there are many of them.
#ifndef GPS_DEBUG
if (reader.failedChecksum() > 4)
#endif
LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
reader.failedChecksum());
lastChecksumFailCount = reader.failedChecksum();
}
#endif

View File

@@ -109,6 +109,35 @@ RTCSetResult readFromRTC()
}
return RTCSetResultSuccess;
}
#elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) {
uint32_t now = millis();
ArtronShop_RX8130CE rtc(&Wire);
tm t;
if (rtc.getTime(&t)) {
tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
LOG_DEBUG("Read RTC time from RX8130CE getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900,
t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
}
#endif
if (currentQuality == RTCQualityNone) {
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
currentQuality = RTCQualityDevice;
}
return RTCSetResultSuccess;
}
}
#else
if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis();
@@ -214,6 +243,17 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
}
#elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) {
ArtronShop_RX8130CE rtc(&Wire);
tm *t = gmtime(&tv->tv_sec);
if (rtc.setTime(*t)) {
LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1,
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
} else {
LOG_WARN("Failed to set time for RX8130CE");
}
}
#elif defined(ARCH_ESP32)
settimeofday(tv, NULL);
#endif

View File

@@ -4,6 +4,10 @@
#include "sys/time.h"
#include <Arduino.h>
#ifdef RX8130CE_RTC
#include <ArtronShop_RX8130CE.h>
#endif
enum RTCQuality {
/// We haven't had our RTC set yet

View File

@@ -243,7 +243,7 @@ bool EInkDisplay::connect()
adafruitDisplay->setRotation(1);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
}
#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
{
spi1 = &SPI1;
spi1->begin();

View File

@@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay
SPIClass *hspi = NULL;
#endif
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
SPIClass *spi1 = NULL;
#endif

687
src/graphics/Panel_sdl.cpp Normal file
View File

@@ -0,0 +1,687 @@
/*----------------------------------------------------------------------------/
Lovyan GFX - Graphics library for embedded devices.
Original Source:
https://github.com/lovyan03/LovyanGFX/
Licence:
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
Author:
[lovyan03](https://twitter.com/lovyan03)
Contributors:
[ciniml](https://github.com/ciniml)
[mongonta0716](https://github.com/mongonta0716)
[tobozo](https://github.com/tobozo)
Porting for SDL:
[imliubo](https://github.com/imliubo)
/----------------------------------------------------------------------------*/
#include "Panel_sdl.hpp"
#if defined(SDL_h_)
// #include "../common.hpp"
// #include "../../misc/common_function.hpp"
// #include "../../Bus.hpp"
#include <list>
#include <math.h>
#include <vector>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace lgfx
{
inline namespace v1
{
SDL_Keymod Panel_sdl::_keymod = KMOD_NONE;
static SDL_semaphore *_update_in_semaphore = nullptr;
static SDL_semaphore *_update_out_semaphore = nullptr;
volatile static uint32_t _in_step_exec = 0;
volatile static uint32_t _msec_step_exec = 512;
static bool _inited = false;
static bool _all_close = false;
volatile uint8_t Panel_sdl::_gpio_dummy_values[EMULATED_GPIO_MAX];
static inline void *heap_alloc_dma(size_t length)
{
return malloc(length);
} // aligned_alloc(16, length);
static inline void heap_free(void *buf)
{
free(buf);
}
static std::list<monitor_t *> _list_monitor;
static monitor_t *const getMonitorByWindowID(uint32_t windowID)
{
for (auto &m : _list_monitor) {
if (SDL_GetWindowID(m->window) == windowID) {
return m;
}
}
return nullptr;
}
//----------------------------------------------------------------------------
static std::vector<Panel_sdl::KeyCodeMapping_t> _key_code_map;
void Panel_sdl::addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio)
{
if (gpio > EMULATED_GPIO_MAX)
return;
KeyCodeMapping_t map;
map.keycode = keyCode;
map.gpio = gpio;
_key_code_map.push_back(map);
}
int Panel_sdl::getKeyCodeMapping(SDL_KeyCode keyCode)
{
for (const auto &i : _key_code_map) {
if (i.keycode == keyCode)
return i.gpio;
}
return -1;
}
void Panel_sdl::_event_proc(void)
{
SDL_Event event;
while (SDL_PollEvent(&event)) {
if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) {
auto mon = getMonitorByWindowID(event.button.windowID);
int gpio = -1;
/// Check key mapping
gpio = getKeyCodeMapping((SDL_KeyCode)event.key.keysym.sym);
if (gpio < 0) {
switch (event.key.keysym.sym) { /// M5StackのBtnABtnCのエミュレート;
// case SDLK_LEFT: gpio = 39; break;
// case SDLK_DOWN: gpio = 38; break;
// case SDLK_RIGHT: gpio = 37; break;
// case SDLK_UP: gpio = 36; break;
/// L/Rキーで画面回転
case SDLK_r:
case SDLK_l:
if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) {
if (mon != nullptr) {
mon->frame_rotation = (mon->frame_rotation += event.key.keysym.sym == SDLK_r ? 1 : -1);
int x, y, w, h;
SDL_GetWindowSize(mon->window, &w, &h);
SDL_GetWindowPosition(mon->window, &x, &y);
SDL_SetWindowSize(mon->window, h, w);
SDL_SetWindowPosition(mon->window, x + (w - h) / 2, y + (h - w) / 2);
mon->panel->sdl_invalidate();
}
}
break;
/// 16キーで画面拡大率変更
case SDLK_1:
case SDLK_2:
case SDLK_3:
case SDLK_4:
case SDLK_5:
case SDLK_6:
if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) {
if (mon != nullptr) {
int size = 1 + (event.key.keysym.sym - SDLK_1);
_update_scaling(mon, size, size);
}
}
break;
default:
continue;
}
}
if (event.type == SDL_KEYDOWN) {
Panel_sdl::gpio_lo(gpio);
} else {
Panel_sdl::gpio_hi(gpio);
}
} else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION) {
auto mon = getMonitorByWindowID(event.button.windowID);
if (mon != nullptr) {
{
int x, y, w, h;
SDL_GetWindowSize(mon->window, &w, &h);
SDL_GetMouseState(&x, &y);
float sf = sinf(mon->frame_angle * M_PI / 180);
float cf = cosf(mon->frame_angle * M_PI / 180);
x -= w / 2.0f;
y -= h / 2.0f;
float nx = y * sf + x * cf;
float ny = y * cf - x * sf;
if (mon->frame_rotation & 1) {
std::swap(w, h);
}
x = (nx * mon->frame_width / w) + (mon->frame_width >> 1);
y = (ny * mon->frame_height / h) + (mon->frame_height >> 1);
mon->touch_x = x - mon->frame_inner_x;
mon->touch_y = y - mon->frame_inner_y;
}
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) {
mon->touched = true;
}
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) {
mon->touched = false;
}
}
} else if (event.type == SDL_WINDOWEVENT) {
auto monitor = getMonitorByWindowID(event.window.windowID);
if (monitor) {
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
int mw, mh;
SDL_GetRendererOutputSize(monitor->renderer, &mw, &mh);
if (monitor->frame_rotation & 1) {
std::swap(mw, mh);
}
monitor->scaling_x = (mw * 2 / monitor->frame_width) / 2.0f;
monitor->scaling_y = (mh * 2 / monitor->frame_height) / 2.0f;
monitor->panel->sdl_invalidate();
} else if (event.window.event == SDL_WINDOWEVENT_CLOSE) {
monitor->closing = true;
}
}
} else if (event.type == SDL_QUIT) {
for (auto &m : _list_monitor) {
m->closing = true;
}
}
}
}
/// デバッガでステップ実行されていることを検出するスレッド用関数。
static int detectDebugger(bool *running)
{
uint32_t prev_ms = SDL_GetTicks();
do {
SDL_Delay(1);
uint32_t ms = SDL_GetTicks();
/// 時間間隔が広すぎる場合はステップ実行中 (ブレークポイントで止まった)と判断する。
/// また、解除されたと判断した後も1023msecほど状態を維持する。
if (ms - prev_ms > 64) {
_in_step_exec = _msec_step_exec;
} else if (_in_step_exec) {
--_in_step_exec;
}
prev_ms = ms;
} while (*running);
return 0;
}
void Panel_sdl::_update_proc(void)
{
for (auto it = _list_monitor.begin(); it != _list_monitor.end();) {
if ((*it)->closing) {
if ((*it)->texture_frameimage) {
SDL_DestroyTexture((*it)->texture_frameimage);
}
SDL_DestroyTexture((*it)->texture);
SDL_DestroyRenderer((*it)->renderer);
SDL_DestroyWindow((*it)->window);
_list_monitor.erase(it++);
if (_list_monitor.empty()) {
_all_close = true;
return;
}
continue;
}
(*it)->panel->sdl_update();
++it;
}
}
int Panel_sdl::setup(void)
{
if (_inited)
return 1;
_inited = true;
/// Add default keycode mapping
/// M5StackのBtnABtnCのエミュレート;
addKeyCodeMapping(SDLK_LEFT, 39);
addKeyCodeMapping(SDLK_DOWN, 38);
addKeyCodeMapping(SDLK_RIGHT, 37);
addKeyCodeMapping(SDLK_UP, 36);
SDL_CreateThread((SDL_ThreadFunction)detectDebugger, "dbg", &_inited);
_update_in_semaphore = SDL_CreateSemaphore(0);
_update_out_semaphore = SDL_CreateSemaphore(0);
for (size_t pin = 0; pin < EMULATED_GPIO_MAX; ++pin) {
gpio_hi(pin);
}
/*Initialize the SDL*/
SDL_Init(SDL_INIT_VIDEO);
SDL_StartTextInput();
// SDL_SetThreadPriority(SDL_ThreadPriority::SDL_THREAD_PRIORITY_HIGH);
return 0;
}
int Panel_sdl::loop(void)
{
if (!_inited)
return 1;
_event_proc();
SDL_SemWaitTimeout(_update_in_semaphore, 1);
_update_proc();
_event_proc();
if (SDL_SemValue(_update_out_semaphore) == 0) {
SDL_SemPost(_update_out_semaphore);
}
return _all_close;
}
int Panel_sdl::close(void)
{
if (!_inited)
return 1;
_inited = false;
SDL_StopTextInput();
SDL_DestroySemaphore(_update_in_semaphore);
SDL_DestroySemaphore(_update_out_semaphore);
SDL_Quit();
return 0;
}
int Panel_sdl::main(int (*fn)(bool *), uint32_t msec_step_exec)
{
_msec_step_exec = msec_step_exec;
/// SDLの準備
if (0 != Panel_sdl::setup()) {
return 1;
}
/// ユーザコード関数の動作・停止フラグ
bool running = true;
/// ユーザコード関数を起動する
auto thread = SDL_CreateThread((SDL_ThreadFunction)fn, "fn", &running);
/// 全部のウィンドウが閉じられるまでSDLのイベント・描画処理を継続
while (0 == Panel_sdl::loop()) {
};
/// ユーザコード関数を終了する
running = false;
SDL_WaitThread(thread, nullptr);
/// SDLを終了する
return Panel_sdl::close();
}
void Panel_sdl::setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y)
{
monitor.scaling_x = scaling_x;
monitor.scaling_y = scaling_y;
}
void Panel_sdl::setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y)
{
monitor.frame_image = frame_image;
monitor.frame_width = frame_width;
monitor.frame_height = frame_height;
monitor.frame_inner_x = inner_x;
monitor.frame_inner_y = inner_y;
}
void Panel_sdl::setFrameRotation(uint_fast16_t frame_rotation)
{
monitor.frame_rotation = frame_rotation;
monitor.frame_angle = (monitor.frame_rotation) * 90;
}
Panel_sdl::~Panel_sdl(void)
{
_list_monitor.remove(&monitor);
SDL_DestroyMutex(_sdl_mutex);
}
Panel_sdl::Panel_sdl(void) : Panel_FrameBufferBase()
{
_sdl_mutex = SDL_CreateMutex();
_auto_display = true;
monitor.panel = this;
}
bool Panel_sdl::init(bool use_reset)
{
initFrameBuffer(_cfg.panel_width * 4, _cfg.panel_height);
bool res = Panel_FrameBufferBase::init(use_reset);
_list_monitor.push_back(&monitor);
return res;
}
color_depth_t Panel_sdl::setColorDepth(color_depth_t depth)
{
auto bits = depth & color_depth_t::bit_mask;
if (bits >= 16) {
depth = (bits > 16) ? rgb888_3Byte : rgb565_2Byte;
} else {
depth = (depth == color_depth_t::grayscale_8bit) ? grayscale_8bit : rgb332_1Byte;
}
_write_depth = depth;
_read_depth = depth;
return depth;
}
Panel_sdl::lock_t::lock_t(Panel_sdl *parent) : _parent{parent}
{
SDL_LockMutex(parent->_sdl_mutex);
};
Panel_sdl::lock_t::~lock_t(void)
{
++_parent->_modified_counter;
SDL_UnlockMutex(_parent->_sdl_mutex);
if (SDL_SemValue(_update_in_semaphore) < 2) {
SDL_SemPost(_update_in_semaphore);
if (!_in_step_exec) {
SDL_SemWaitTimeout(_update_out_semaphore, 1);
}
}
};
void Panel_sdl::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor)
{
lock_t lock(this);
Panel_FrameBufferBase::drawPixelPreclipped(x, y, rawcolor);
}
void Panel_sdl::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor)
{
lock_t lock(this);
Panel_FrameBufferBase::writeFillRectPreclipped(x, y, w, h, rawcolor);
}
void Panel_sdl::writeBlock(uint32_t rawcolor, uint32_t length)
{
// lock_t lock(this);
Panel_FrameBufferBase::writeBlock(rawcolor, length);
}
void Panel_sdl::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param, bool use_dma)
{
lock_t lock(this);
Panel_FrameBufferBase::writeImage(x, y, w, h, param, use_dma);
}
void Panel_sdl::writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param)
{
lock_t lock(this);
Panel_FrameBufferBase::writeImageARGB(x, y, w, h, param);
}
void Panel_sdl::writePixels(pixelcopy_t *param, uint32_t len, bool use_dma)
{
lock_t lock(this);
Panel_FrameBufferBase::writePixels(param, len, use_dma);
}
void Panel_sdl::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h)
{
(void)x;
(void)y;
(void)w;
(void)h;
if (_in_step_exec) {
if (_display_counter != _modified_counter) {
do {
SDL_SemPost(_update_in_semaphore);
SDL_SemWaitTimeout(_update_out_semaphore, 1);
} while (_display_counter != _modified_counter);
SDL_Delay(1);
}
}
}
uint_fast8_t Panel_sdl::getTouchRaw(touch_point_t *tp, uint_fast8_t count)
{
(void)count;
tp->x = monitor.touch_x;
tp->y = monitor.touch_y;
tp->size = monitor.touched ? 1 : 0;
tp->id = 0;
return monitor.touched;
}
void Panel_sdl::setWindowTitle(const char *title)
{
_window_title = title;
if (monitor.window) {
SDL_SetWindowTitle(monitor.window, _window_title);
}
}
void Panel_sdl::_update_scaling(monitor_t *mon, float sx, float sy)
{
mon->scaling_x = sx;
mon->scaling_y = sy;
int nw = mon->frame_width;
int nh = mon->frame_height;
if (mon->frame_rotation & 1) {
std::swap(nw, nh);
}
int x, y, w, h;
int rw, rh;
SDL_GetRendererOutputSize(mon->renderer, &rw, &rh);
SDL_GetWindowSize(mon->window, &w, &h);
nw = nw * sx * w / rw;
nh = nh * sy * h / rh;
SDL_GetWindowPosition(mon->window, &x, &y);
SDL_SetWindowSize(mon->window, nw, nh);
SDL_SetWindowPosition(mon->window, x + (w - nw) / 2, y + (h - nh) / 2);
mon->panel->sdl_invalidate();
}
void Panel_sdl::sdl_create(monitor_t *m)
{
int flag = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
#if SDL_FULLSCREEN
flag |= SDL_WINDOW_FULLSCREEN;
#endif
if (m->frame_width < _cfg.panel_width) {
m->frame_width = _cfg.panel_width;
}
if (m->frame_height < _cfg.panel_height) {
m->frame_height = _cfg.panel_height;
}
int window_width = m->frame_width * m->scaling_x;
int window_height = m->frame_height * m->scaling_y;
int scaling_x = m->scaling_x;
int scaling_y = m->scaling_y;
if (m->frame_rotation & 1) {
std::swap(window_width, window_height);
std::swap(scaling_x, scaling_y);
}
{
m->window = SDL_CreateWindow(_window_title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height,
flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/
}
m->renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
m->texture =
SDL_CreateTexture(m->renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, _cfg.panel_width, _cfg.panel_height);
SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_NONE);
if (m->frame_image) {
// 枠画像用のサーフェイスを作成
auto sf = SDL_CreateRGBSurfaceFrom((void *)m->frame_image, m->frame_width, m->frame_height, 32, m->frame_width * 4,
0xFF000000, 0xFF0000, 0xFF00, 0xFF);
if (sf != nullptr) {
// 枠画像からテクスチャを作成
m->texture_frameimage = SDL_CreateTextureFromSurface(m->renderer, sf);
SDL_FreeSurface(sf);
}
}
SDL_SetTextureBlendMode(m->texture_frameimage, SDL_BLENDMODE_BLEND);
_update_scaling(m, scaling_x, scaling_y);
}
void Panel_sdl::sdl_update(void)
{
if (monitor.renderer == nullptr) {
sdl_create(&monitor);
}
bool step_exec = _in_step_exec;
if (_texupdate_counter != _modified_counter) {
pixelcopy_t pc(nullptr, color_depth_t::rgb888_3Byte, _write_depth, false);
if (_write_depth == rgb565_2Byte) {
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, swap565_t>;
} else if (_write_depth == rgb888_3Byte) {
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, bgr888_t>;
} else if (_write_depth == rgb332_1Byte) {
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, rgb332_t>;
} else if (_write_depth == grayscale_8bit) {
pc.fp_copy = pixelcopy_t::copy_rgb_fast<bgr888_t, grayscale_t>;
}
if (0 == SDL_LockMutex(_sdl_mutex)) {
_texupdate_counter = _modified_counter;
for (int y = 0; y < _cfg.panel_height; ++y) {
pc.src_x32 = 0;
pc.src_data = _lines_buffer[y];
pc.fp_copy(&_texturebuf[y * _cfg.panel_width], 0, _cfg.panel_width, &pc);
}
SDL_UnlockMutex(_sdl_mutex);
SDL_UpdateTexture(monitor.texture, nullptr, _texturebuf, _cfg.panel_width * sizeof(rgb888_t));
}
}
int angle = monitor.frame_angle;
int target = (monitor.frame_rotation) * 90;
angle = (((target * 4) + (angle * 4) + (angle < target ? 8 : 0)) >> 3);
if (monitor.frame_angle != angle) { // 表示する向きを変える
monitor.frame_angle = angle;
sdl_invalidate();
} else if (monitor.frame_rotation & ~3u) {
monitor.frame_rotation &= 3;
monitor.frame_angle = (monitor.frame_rotation) * 90;
sdl_invalidate();
}
if (_invalidated || (_display_counter != _texupdate_counter)) {
SDL_RendererInfo info;
if (0 == SDL_GetRendererInfo(monitor.renderer, &info)) {
// ステップ実行中はVSYNCを待機しない
if (((bool)(info.flags & SDL_RENDERER_PRESENTVSYNC)) == step_exec) {
SDL_RenderSetVSync(monitor.renderer, !step_exec);
}
}
{
int red = 0;
int green = 0;
int blue = 0;
#if defined(M5GFX_BACK_COLOR)
red = ((M5GFX_BACK_COLOR) >> 16) & 0xFF;
green = ((M5GFX_BACK_COLOR) >> 8) & 0xFF;
blue = ((M5GFX_BACK_COLOR)) & 0xFF;
#endif
SDL_SetRenderDrawColor(monitor.renderer, red, green, blue, 0xFF);
}
SDL_RenderClear(monitor.renderer);
if (_invalidated) {
_invalidated = false;
int mw, mh;
SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh);
}
render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height, angle);
render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle);
SDL_RenderPresent(monitor.renderer);
_display_counter = _texupdate_counter;
if (_invalidated) {
_invalidated = false;
SDL_SetRenderDrawColor(monitor.renderer, 0, 0, 0, 0xFF);
SDL_RenderClear(monitor.renderer);
render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height,
angle);
render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle);
SDL_RenderPresent(monitor.renderer);
}
}
}
void Panel_sdl::render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle)
{
SDL_Point pivot;
pivot.x = (monitor.frame_width / 2.0f - tx) * (float)monitor.scaling_x;
pivot.y = (monitor.frame_height / 2.0f - ty) * (float)monitor.scaling_y;
SDL_Rect dstrect;
dstrect.w = tw * monitor.scaling_x;
dstrect.h = th * monitor.scaling_y;
int mw, mh;
SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh);
dstrect.x = mw / 2.0f - pivot.x;
dstrect.y = mh / 2.0f - pivot.y;
SDL_RenderCopyEx(monitor.renderer, texture, nullptr, &dstrect, angle, &pivot, SDL_RendererFlip::SDL_FLIP_NONE);
}
bool Panel_sdl::initFrameBuffer(size_t width, size_t height)
{
uint8_t **lineArray = (uint8_t **)heap_alloc_dma(height * sizeof(uint8_t *));
if (nullptr == lineArray) {
return false;
}
_texturebuf = (rgb888_t *)heap_alloc_dma(width * height * sizeof(rgb888_t));
/// 8byte alignment;
width = (width + 7) & ~7u;
_lines_buffer = lineArray;
memset(lineArray, 0, height * sizeof(uint8_t *));
uint8_t *framebuffer = (uint8_t *)heap_alloc_dma(width * height + 16);
auto fb = framebuffer;
{
for (size_t y = 0; y < height; ++y) {
lineArray[y] = fb;
fb += width;
}
}
return true;
}
void Panel_sdl::deinitFrameBuffer(void)
{
auto lines = _lines_buffer;
_lines_buffer = nullptr;
if (lines != nullptr) {
heap_free(lines[0]);
heap_free(lines);
}
if (_texturebuf) {
heap_free(_texturebuf);
_texturebuf = nullptr;
}
}
//----------------------------------------------------------------------------
} // namespace v1
} // namespace lgfx
#endif

166
src/graphics/Panel_sdl.hpp Normal file
View File

@@ -0,0 +1,166 @@
/*----------------------------------------------------------------------------/
Lovyan GFX - Graphics library for embedded devices.
Original Source:
https://github.com/lovyan03/LovyanGFX/
Licence:
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
Author:
[lovyan03](https://twitter.com/lovyan03)
Contributors:
[ciniml](https://github.com/ciniml)
[mongonta0716](https://github.com/mongonta0716)
[tobozo](https://github.com/tobozo)
Porting for SDL:
[imliubo](https://github.com/imliubo)
/----------------------------------------------------------------------------*/
#pragma once
#define SDL_MAIN_HANDLED
// cppcheck-suppress preprocessorErrorDirective
#if __has_include(<SDL2/SDL.h>)
#include <SDL2/SDL.h>
#include <SDL2/SDL_main.h>
#elif __has_include(<SDL.h>)
#include <SDL.h>
#include <SDL_main.h>
#endif
#if defined(SDL_h_)
#include "lgfx/v1/Touch.hpp"
#include "lgfx/v1/misc/range.hpp"
#include "lgfx/v1/panel/Panel_FrameBufferBase.hpp"
#include <cstdint>
namespace lgfx
{
inline namespace v1
{
struct Panel_sdl;
struct monitor_t {
SDL_Window *window = nullptr;
SDL_Renderer *renderer = nullptr;
SDL_Texture *texture = nullptr;
SDL_Texture *texture_frameimage = nullptr;
Panel_sdl *panel = nullptr;
// 外枠
const void *frame_image = 0;
uint_fast16_t frame_width = 0;
uint_fast16_t frame_height = 0;
uint_fast16_t frame_inner_x = 0;
uint_fast16_t frame_inner_y = 0;
int_fast16_t frame_rotation = 0;
int_fast16_t frame_angle = 0;
float scaling_x = 1;
float scaling_y = 1;
int_fast16_t touch_x, touch_y;
bool touched = false;
bool closing = false;
};
//----------------------------------------------------------------------------
struct Touch_sdl : public ITouch {
bool init(void) override { return true; }
void wakeup(void) override {}
void sleep(void) override {}
bool isEnable(void) override { return true; };
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override { return 0; }
};
//----------------------------------------------------------------------------
struct Panel_sdl : public Panel_FrameBufferBase {
static constexpr size_t EMULATED_GPIO_MAX = 128;
static volatile uint8_t _gpio_dummy_values[EMULATED_GPIO_MAX];
public:
Panel_sdl(void);
virtual ~Panel_sdl(void);
bool init(bool use_reset) override;
color_depth_t setColorDepth(color_depth_t depth) override;
void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override;
// void setInvert(bool invert) override {}
void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override;
void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override;
void writeBlock(uint32_t rawcolor, uint32_t length) override;
void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param,
bool use_dma) override;
void writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param) override;
void writePixels(pixelcopy_t *param, uint32_t len, bool use_dma) override;
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override;
void setWindowTitle(const char *title);
void setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y);
void setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y);
void setFrameRotation(uint_fast16_t frame_rotaion);
void setBrightness(uint8_t brightness) override{};
static volatile void gpio_hi(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 1; }
static volatile void gpio_lo(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 0; }
static volatile bool gpio_in(uint32_t pin) { return _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)]; }
static int setup(void);
static int loop(void);
static int close(void);
static int main(int (*fn)(bool *), uint32_t msec_step_exec = 512);
static void setShortcutKeymod(SDL_Keymod keymod) { _keymod = keymod; }
struct KeyCodeMapping_t {
SDL_KeyCode keycode = SDLK_UNKNOWN;
uint8_t gpio = 0;
};
static void addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio);
static int getKeyCodeMapping(SDL_KeyCode keyCode);
protected:
const char *_window_title = "LGFX Simulator";
SDL_mutex *_sdl_mutex = nullptr;
void sdl_create(monitor_t *m);
void sdl_update(void);
touch_point_t _touch_point;
monitor_t monitor;
rgb888_t *_texturebuf = nullptr;
uint_fast16_t _modified_counter;
uint_fast16_t _texupdate_counter;
uint_fast16_t _display_counter;
bool _invalidated;
static void _event_proc(void);
static void _update_proc(void);
static void _update_scaling(monitor_t *m, float sx, float sy);
void sdl_invalidate(void) { _invalidated = true; }
void render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle);
bool initFrameBuffer(size_t width, size_t height);
void deinitFrameBuffer(void);
static SDL_Keymod _keymod;
struct lock_t {
lock_t(Panel_sdl *parent);
~lock_t();
protected:
Panel_sdl *_parent;
};
};
//----------------------------------------------------------------------------
} // namespace v1
} // namespace lgfx
#endif

View File

@@ -83,6 +83,11 @@ extern uint16_t TFT_MESH;
#include "platform/portduino/PortduinoGlue.h"
#endif
#if defined(T_LORA_PAGER)
// KB backlight control
#include "input/cardKbI2cImpl.h"
#endif
using namespace meshtastic; /** @todo remove */
namespace graphics
@@ -95,7 +100,7 @@ namespace graphics
#define NUM_EXTRA_FRAMES 3 // text message and debug frame
// if defined a pixel will blink to show redraws
// #define SHOW_REDRAWS
#define ASCII_BELL '\x07'
// A text message frame + debug frame + all the node infos
FrameCallback *normalFrames;
static uint32_t targetFramerate = IDLE_FRAMERATE;
@@ -448,7 +453,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#endif
dispdev->displayOn();
#ifdef HELTEC_TRACKER_V1_X
#if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2)
ui->init();
#endif
#ifdef USE_ST7789
@@ -655,6 +660,19 @@ void Screen::setup()
MeshModule::observeUIEvents(&uiFrameEventObserver);
}
void Screen::setOn(bool on, FrameCallback einkScreensaver)
{
#if defined(T_LORA_PAGER)
if (cardKbI2cImpl)
cardKbI2cImpl->toggleBacklight(on);
#endif
if (!on)
// We handle off commands immediately, because they might be called because the CPU is shutting down
handleSetOn(false, einkScreensaver);
else
enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON});
}
void Screen::forceDisplay(bool forceUiUpdate)
{
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
@@ -1440,28 +1458,36 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
}
// === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const meshtastic_Channel channel =
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
char banner[256];
// Check for bell character in message to determine alert type
bool isAlert = false;
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == '\x07') {
isAlert = true;
break;
}
}
if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_bell_vibra ||
moduleConfig.external_notification.alert_bell_buzzer)
// Check for bell character to determine if this message is an alert
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == ASCII_BELL) {
isAlert = true;
break;
}
}
// Unlike generic messages, alerts (when enabled via the ext notif module) ignore any
// 'mute' preferences set to any specific node or channel.
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
}
} else {
screen->showSimpleBanner(banner, 3000);
} else if (!channel.settings.mute) {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
@@ -1472,14 +1498,21 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
} else {
strcpy(banner, "New Message");
}
}
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
playLongBeep();
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
(!isBroadcast(packet->to) && isToUs(p))) {
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
// - packet contains an alert and alert bell buzzer is enabled
// - packet is a non-broadcast that is addressed to this node
playLongBeep();
}
#else
screen->showSimpleBanner(banner, 3000);
screen->showSimpleBanner(banner, 3000);
#endif
}
}
}

View File

@@ -259,15 +259,7 @@ class Screen : public concurrency::OSThread
void setup();
/// Turns the screen on/off. Optionally, pass a custom screensaver frame for E-Ink
void setOn(bool on, FrameCallback einkScreensaver = NULL)
{
if (!on)
// We handle off commands immediately, because they might be called because the CPU is shutting down
handleSetOn(false, einkScreensaver);
else
enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON});
}
void setOn(bool on, FrameCallback einkScreensaver = NULL);
/**
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code

View File

@@ -284,7 +284,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int iconX = iconRightEdge - mute_symbol_big_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
if (isInverted) {
if (isInverted && !force_no_invert) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2);
display->setColor(BLACK);

View File

@@ -751,10 +751,8 @@ static LGFX *tft = nullptr;
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
#elif ARCH_PORTDUINO
#include "Panel_sdl.hpp"
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
#if defined(LGFX_SDL)
#include <lgfx/v1/platforms/sdl/Panel_sdl.hpp>
#endif
class LGFX : public lgfx::LGFX_Device
{
@@ -783,10 +781,10 @@ class LGFX : public lgfx::LGFX_Device
_panel_instance = new lgfx::Panel_ILI9488;
else if (portduino_config.displayPanel == hx8357d)
_panel_instance = new lgfx::Panel_HX8357D;
#if defined(LGFX_SDL)
else if (portduino_config.displayPanel == x11) {
#if defined(SDL_h_)
else if (portduino_config.displayPanel == x11)
_panel_instance = new lgfx::Panel_sdl;
}
#endif
else {
_panel_instance = new lgfx::Panel_NULL;
@@ -799,8 +797,9 @@ class LGFX : public lgfx::LGFX_Device
buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable)
_bus_instance.config(buscfg); // applies the set value to the bus.
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
_bus_instance.config(buscfg); // applies the set value to the bus.
if (portduino_config.displayPanel != x11)
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
LOG_DEBUG("Width: %d, Height: %d", portduino_config.displayWidth, portduino_config.displayHeight);
@@ -848,7 +847,7 @@ class LGFX : public lgfx::LGFX_Device
_touch_instance->config(touch_cfg);
_panel_instance->setTouch(_touch_instance);
}
#if defined(LGFX_SDL)
#if defined(SDL_h_)
if (portduino_config.displayPanel == x11) {
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
sdl_panel_->setup();
@@ -1237,7 +1236,7 @@ void TFTDisplay::display(bool fromBlank)
void TFTDisplay::sdlLoop()
{
#if defined(LGFX_SDL)
#if defined(SDL_h_)
static int lastPressed = 0;
static int shuttingDown = false;
if (portduino_config.displayPanel == x11) {
@@ -1247,27 +1246,26 @@ void TFTDisplay::sdlLoop()
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
}
// debounce
if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed))
if (lastPressed != 0 && !sdl_panel_->gpio_in(lastPressed))
return;
if (!lgfx::v1::gpio_in(37)) {
if (!sdl_panel_->gpio_in(37)) {
lastPressed = 37;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!lgfx::v1::gpio_in(36)) {
} else if (!sdl_panel_->gpio_in(36)) {
lastPressed = 36;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!lgfx::v1::gpio_in(38)) {
} else if (!sdl_panel_->gpio_in(38)) {
lastPressed = 38;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!lgfx::v1::gpio_in(39)) {
} else if (!sdl_panel_->gpio_in(39)) {
lastPressed = 39;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) {
} else if (!sdl_panel_->gpio_in(SDL_SCANCODE_KP_ENTER)) {
lastPressed = SDL_SCANCODE_KP_ENTER;
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);

View File

@@ -116,6 +116,8 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) {
config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected);
auto changes = SEGMENT_CONFIG;
// This is needed as we wait til picking the LoRa region to generate keys for the first time.
if (!owner.is_licensed) {
bool keygenSuccess = false;
@@ -124,6 +126,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
keygenSuccess = true;
}
} else {
LOG_INFO("Generate new PKI keys");
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
@@ -141,7 +144,14 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
if (myRegion->dutyCycle < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
}
service->reloadConfig(SEGMENT_CONFIG);
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
// Default broker is in use, so subscribe to the appropriate MQTT root topic for this region
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
changes |= SEGMENT_MODULECONFIG;
}
service->reloadConfig(changes);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
}
};
@@ -852,24 +862,31 @@ void menuHandler::GPSFormatMenu()
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 2) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 3) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 4) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 5) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 6) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 7) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else {
menuQueue = position_base_menu;
@@ -904,11 +921,11 @@ void menuHandler::BluetoothToggleMenu()
void menuHandler::BuzzerModeMenu()
{
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"};
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only", "DMs Only"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Buzzer Mode";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void {
config.device.buzzer_mode = (meshtastic_Config_DeviceConfig_BuzzerMode)selected;
service->reloadConfig(SEGMENT_CONFIG);

View File

@@ -125,8 +125,10 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
char displayLine[32];
if (!gps->getIsConnected() && !config.position.fixed_position) {
strcpy(displayLine, "No GPS present");
display->drawString(x, y, displayLine);
if (strcmp(mode, "line1") == 0) {
strcpy(displayLine, "No GPS present");
display->drawString(x, y, displayLine);
}
} else if (!gps->getHasLock() && !config.position.fixed_position) {
if (strcmp(mode, "line1") == 0) {
strcpy(displayLine, "No GPS Lock");
@@ -1103,6 +1105,18 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
// === Fourth Row: Line 2 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2");
}
// === Final Row: Altitude ===
char altitudeLine[32] = {0};
int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
? ourNode->position.altitude
: geoCoord.getAltitude();
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0fft", alt * METERS_TO_FEET);
} else {
snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0im", alt);
}
display->drawString(x, getTextPositions(display)[line++], altitudeLine);
}
#if !defined(M5STACK_UNITC6L)
// === Draw Compass if heading is valid ===

View File

@@ -274,7 +274,12 @@ int32_t ButtonThread::runOnce()
}
}
btnEvent = BUTTON_EVENT_NONE;
return 50;
// only pull when the button is pressed, we get notified via IRQ on a new press
if (!userButton.isIdle() || waitingForLongPress) {
return 50;
}
return 100; // FIXME: Why can't we rely on interrupts and use INT32_MAX here?
}
/*

View File

@@ -3,16 +3,66 @@
InputBroker *inputBroker = nullptr;
InputBroker::InputBroker(){};
InputBroker::InputBroker()
{
#ifdef HAS_FREE_RTOS
inputEventQueue = xQueueCreate(5, sizeof(InputEvent));
pollSoonQueue = xQueueCreate(5, sizeof(InputPollable *));
xTaskCreate(pollSoonWorker, "input-pollSoon", 2 * 1024, this, 10, &pollSoonTask);
#endif
}
void InputBroker::registerSource(Observable<const InputEvent *> *source)
{
this->inputEventObserver.observe(source);
}
#ifdef HAS_FREE_RTOS
void InputBroker::requestPollSoon(InputPollable *pollable)
{
if (xPortInIsrContext() == pdTRUE) {
xQueueSendFromISR(pollSoonQueue, &pollable, NULL);
} else {
xQueueSend(pollSoonQueue, &pollable, 0);
}
}
void InputBroker::queueInputEvent(const InputEvent *event)
{
if (xPortInIsrContext() == pdTRUE) {
xQueueSendFromISR(inputEventQueue, event, NULL);
} else {
xQueueSend(inputEventQueue, event, portMAX_DELAY);
}
}
void InputBroker::processInputEventQueue()
{
InputEvent event;
while (xQueueReceive(inputEventQueue, &event, 0)) {
handleInputEvent(&event);
}
}
#endif
int InputBroker::handleInputEvent(const InputEvent *event)
{
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
this->notifyObservers(event);
return 0;
}
}
#ifdef HAS_FREE_RTOS
void InputBroker::pollSoonWorker(void *p)
{
InputBroker *instance = (InputBroker *)p;
while (true) {
InputPollable *pollable = NULL;
xQueueReceive(instance->pollSoonQueue, &pollable, portMAX_DELAY);
if (pollable) {
pollable->pollOnce();
}
}
vTaskDelete(NULL);
}
#endif

View File

@@ -1,5 +1,7 @@
#pragma once
#include "Observer.h"
#include "freertosinc.h"
enum input_broker_event {
INPUT_BROKER_NONE = 0,
@@ -41,6 +43,13 @@ typedef struct _InputEvent {
uint16_t touchX;
uint16_t touchY;
} InputEvent;
class InputPollable
{
public:
virtual void pollOnce() = 0;
};
class InputBroker : public Observable<const InputEvent *>
{
CallbackObserver<InputBroker, const InputEvent *> inputEventObserver =
@@ -50,9 +59,22 @@ class InputBroker : public Observable<const InputEvent *>
InputBroker();
void registerSource(Observable<const InputEvent *> *source);
void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
#ifdef HAS_FREE_RTOS
void requestPollSoon(InputPollable *pollable);
void queueInputEvent(const InputEvent *event);
void processInputEventQueue();
#endif
protected:
int handleInputEvent(const InputEvent *event);
private:
#ifdef HAS_FREE_RTOS
QueueHandle_t inputEventQueue;
QueueHandle_t pollSoonQueue;
TaskHandle_t pollSoonTask;
static void pollSoonWorker(void *p);
#endif
};
extern InputBroker *inputBroker;

View File

@@ -8,7 +8,7 @@
RotaryEncoderImpl *rotaryEncoderImpl;
RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME)
RotaryEncoderImpl::RotaryEncoderImpl()
{
rotary = nullptr;
}
@@ -18,7 +18,6 @@ bool RotaryEncoderImpl::init()
if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
moduleConfig.canned_message.inputbroker_pin_b == 0) {
// Input device is disabled.
disable();
return false;
}
@@ -30,7 +29,11 @@ bool RotaryEncoderImpl::init()
moduleConfig.canned_message.inputbroker_pin_press);
rotary->resetButton();
inputBroker->registerSource(this);
interruptInstance = this;
auto interruptHandler = []() { inputBroker->requestPollSoon(interruptInstance); };
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
@@ -38,36 +41,36 @@ bool RotaryEncoderImpl::init()
return true;
}
int32_t RotaryEncoderImpl::runOnce()
void RotaryEncoderImpl::pollOnce()
{
InputEvent e{originName, INPUT_BROKER_NONE, 0, 0, 0};
InputEvent e{ORIGIN_NAME, INPUT_BROKER_NONE, 0, 0, 0};
static uint32_t lastPressed = millis();
if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
if (lastPressed + 200 < millis()) {
LOG_DEBUG("Rotary event Press");
lastPressed = millis();
e.inputEvent = this->eventPressed;
}
} else {
switch (rotary->process()) {
case RotaryEncoder::DIRECTION_CW:
LOG_DEBUG("Rotary event CW");
e.inputEvent = this->eventCw;
break;
case RotaryEncoder::DIRECTION_CCW:
LOG_DEBUG("Rotary event CCW");
e.inputEvent = this->eventCcw;
break;
default:
break;
inputBroker->queueInputEvent(&e);
}
}
if (e.inputEvent != INPUT_BROKER_NONE) {
this->notifyObservers(&e);
switch (rotary->process()) {
case RotaryEncoder::DIRECTION_CW:
LOG_DEBUG("Rotary event CW");
e.inputEvent = this->eventCw;
inputBroker->queueInputEvent(&e);
break;
case RotaryEncoder::DIRECTION_CCW:
LOG_DEBUG("Rotary event CCW");
e.inputEvent = this->eventCcw;
inputBroker->queueInputEvent(&e);
break;
default:
break;
}
return 10;
}
RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
#endif

View File

@@ -1,6 +1,6 @@
#pragma once
// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
// This is a version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
#include "InputBroker.h"
#include "concurrency/OSThread.h"
@@ -8,21 +8,21 @@
class RotaryEncoder;
class RotaryEncoderImpl : public Observable<const InputEvent *>, public concurrency::OSThread
class RotaryEncoderImpl : public InputPollable
{
public:
RotaryEncoderImpl();
bool init(void);
virtual void pollOnce() override;
protected:
virtual int32_t runOnce() override;
static RotaryEncoderImpl *interruptInstance;
input_broker_event eventCw = INPUT_BROKER_NONE;
input_broker_event eventCcw = INPUT_BROKER_NONE;
input_broker_event eventPressed = INPUT_BROKER_NONE;
RotaryEncoder *rotary;
const char *originName;
};
extern RotaryEncoderImpl *rotaryEncoderImpl;

View File

@@ -200,6 +200,11 @@ uint8_t TCA8418KeyboardBase::flush()
return count;
}
void TCA8418KeyboardBase::clearInt()
{
writeRegister(TCA8418_REG_INT_STAT, 3);
}
uint8_t TCA8418KeyboardBase::digitalRead(uint8_t pinnum) const
{
if (pinnum > TCA8418_COL9)

View File

@@ -37,6 +37,8 @@ class TCA8418KeyboardBase
virtual void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = TCA8418_KB_ADDR);
virtual void reset(void);
void clearInt(void);
virtual void trigger(void);
virtual void setBacklight(bool on);

View File

@@ -105,7 +105,14 @@ void TLoraPagerKeyboard::trigger()
void TLoraPagerKeyboard::setBacklight(bool on)
{
toggleBacklight(!on);
uint32_t _brightness = 0;
if (on)
_brightness = brightness;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
ledcWrite(KB_BL_PIN, _brightness);
#else
ledcWrite(LEDC_BACKLIGHT_CHANNEL, _brightness);
#endif
}
void TLoraPagerKeyboard::pressed(uint8_t key)
@@ -192,7 +199,6 @@ void TLoraPagerKeyboard::hapticFeedback()
// toggle brightness of the backlight in three steps
void TLoraPagerKeyboard::toggleBacklight(bool off)
{
static uint32_t brightness = 0;
if (off) {
brightness = 0;
} else {
@@ -206,11 +212,7 @@ void TLoraPagerKeyboard::toggleBacklight(bool off)
}
LOG_DEBUG("Toggle backlight: %d", brightness);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
ledcWrite(KB_BL_PIN, brightness);
#else
ledcWrite(LEDC_BACKLIGHT_CHANNEL, brightness);
#endif
setBacklight(true);
}
void TLoraPagerKeyboard::updateModifierFlag(uint8_t key)

View File

@@ -26,4 +26,5 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase
uint32_t last_tap;
uint8_t char_idx;
int32_t tap_interval;
uint32_t brightness = 0;
};

View File

@@ -333,6 +333,7 @@ int32_t KbI2cBase::runOnce()
}
TCAKeyboard.trigger();
}
TCAKeyboard.clearInt();
break;
}
case 0x02: {
@@ -519,4 +520,11 @@ int32_t KbI2cBase::runOnce()
LOG_WARN("Unknown kb_model 0x%02x", kb_model);
}
return 300;
}
}
void KbI2cBase::toggleBacklight(bool on)
{
#if defined(T_LORA_PAGER)
TCAKeyboard.setBacklight(on);
#endif
}

View File

@@ -12,6 +12,7 @@ class KbI2cBase : public Observable<const InputEvent *>, public concurrency::OST
{
public:
explicit KbI2cBase(const char *name);
void toggleBacklight(bool on);
protected:
virtual int32_t runOnce() override;

View File

@@ -297,6 +297,12 @@ void printInfo()
#ifndef PIO_UNIT_TESTING
void setup()
{
#if defined(R1_NEO)
pinMode(DCDC_EN_HOLD, OUTPUT);
digitalWrite(DCDC_EN_HOLD, HIGH);
pinMode(NRF_ON, OUTPUT);
digitalWrite(NRF_ON, HIGH);
#endif
#if defined(PIN_POWER_EN)
pinMode(PIN_POWER_EN, OUTPUT);
@@ -369,12 +375,13 @@ void setup()
digitalWrite(SDCARD_CS, HIGH);
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH);
pinMode(KB_INT, INPUT_PULLUP);
// io expander
io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL);
io.pinMode(EXPANDS_DRV_EN, OUTPUT);
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
io.pinMode(EXPANDS_AMP_EN, OUTPUT);
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
io.digitalWrite(EXPANDS_AMP_EN, LOW);
io.pinMode(EXPANDS_LORA_EN, OUTPUT);
io.digitalWrite(EXPANDS_LORA_EN, HIGH);
io.pinMode(EXPANDS_GPS_EN, OUTPUT);
@@ -792,14 +799,7 @@ void setup()
}
#endif
// If we're taking on the repeater role, use NextHopRouter and turn off 3V3_S rail because peripherals are not needed
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
router = new NextHopRouter();
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, LOW);
#endif
} else
router = new ReliableRouter();
router = new ReliableRouter();
// only play start melody when role is not tracker or sensor
if (config.power.is_power_saving == true &&
@@ -925,8 +925,7 @@ void setup()
if (sensor_detected == false) {
#endif
if (HAS_GPS) {
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
gps = GPS::createGps();
if (gps) {
gpsStatus->observe(&gps->newStatus);
@@ -1001,6 +1000,7 @@ void setup()
config.pullupSense = INPUT_PULLUP;
config.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1021,6 +1021,7 @@ void setup()
touchConfig.pullupSense = pullup_sense;
touchConfig.intRoutine = []() {
TouchButtonThread->userButton.tick();
TouchButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1040,6 +1041,7 @@ void setup()
cancelConfig.pullupSense = pullup_sense;
cancelConfig.intRoutine = []() {
CancelButtonThread->userButton.tick();
CancelButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1060,6 +1062,7 @@ void setup()
backConfig.pullupSense = pullup_sense;
backConfig.intRoutine = []() {
BackButtonThread->userButton.tick();
BackButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1094,6 +1097,7 @@ void setup()
userConfig.pullupSense = pullup_sense;
userConfig.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1111,6 +1115,7 @@ void setup()
userConfigNoScreen.pullupSense = pullup_sense;
userConfigNoScreen.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
@@ -1595,8 +1600,13 @@ void loop()
#endif
service->loop();
#if defined(LGFX_SDL)
if (screen) {
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS)
if (inputBroker)
inputBroker->processInputEventQueue();
#endif
#if ARCH_PORTDUINO && HAS_TFT
if (screen && portduino_config.displayPanel == x11 &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
auto dispdev = screen->getDisplayDevice();
if (dispdev)
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
@@ -1606,6 +1616,9 @@ void loop()
// We want to sleep as long as possible here - because it saves power
if (!runASAP && loopCanSleep()) {
#ifdef DEBUG_LOOP_TIMING
LOG_DEBUG("main loop delay: %d", delayMsec);
#endif
mainDelay.delay(delayMsec);
}
}

View File

@@ -1,7 +1,12 @@
#include "FloodingRouter.h"
#include "MeshTypes.h"
#include "NodeDB.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
#include "meshUtils.h"
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
#include "modules/TraceRouteModule.h"
#endif
FloodingRouter::FloodingRouter() {}
@@ -21,7 +26,16 @@ ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p)
bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
{
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
bool wasUpgraded = false;
bool seenRecently =
wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected
// Handle hop_limit upgrade scenario for rebroadcasters
if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
return true; // we handled it, so stop processing
}
if (seenRecently) {
printPacket("Ignore dupe incoming msg", p);
rxDupe++;
@@ -31,8 +45,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
if (isRepeated) {
LOG_DEBUG("Repeated reliable tx");
// Check if it's still in the Tx queue, if not, we have to relay it again
if (!findInTxQueue(p->from, p->id))
if (!findInTxQueue(p->from, p->id)) {
reprocessPacket(p);
perhapsRebroadcast(p);
}
} else {
perhapsCancelDupe(p);
}
@@ -43,12 +59,45 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
return Router::shouldFilterReceived(p);
}
bool FloodingRouter::perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p)
{
// isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
if (isRebroadcaster() && iface && p->hop_limit > 0) {
// If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
// rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
p->hop_limit, dropThreshold);
reprocessPacket(p);
perhapsRebroadcast(p);
rxDupe++;
// We already enqueued the improved copy, so make sure the incoming packet stops here.
return true;
}
}
return false;
}
void FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p)
{
if (nodeDB)
nodeDB->updateFrom(*p);
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
traceRouteModule->processUpgradedPacket(*p);
#endif
}
bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
{
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
// ROUTER, REPEATER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
// ROUTER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
// even if we've heard another station rebroadcast it already.
return false;
}
@@ -67,7 +116,7 @@ bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
{
if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA && roleAllowsCancelingDupe(p)) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
// cancel rebroadcast of this message *if* there was already one, unless we're a router!
// But only LoRa packets should be able to trigger this.
if (Router::cancelSending(p->from, p->id))
txRelayCanceled++;
@@ -83,36 +132,6 @@ bool FloodingRouter::isRebroadcaster()
config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
}
void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
{
if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
if (p->id != 0) {
if (isRebroadcaster()) {
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
tosend->hop_limit--; // bump down the hop count
#if USERPREFS_EVENT_MODE
if (tosend->hop_limit > 2) {
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
tosend->hop_start -= (tosend->hop_limit - 2);
tosend->hop_limit = 2;
}
#endif
tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
LOG_INFO("Rebroadcast received floodmsg");
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);
} else {
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
}
} else {
LOG_DEBUG("Ignore 0 id broadcast");
}
}
}
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
@@ -127,4 +146,4 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
// handle the packet as normal
Router::sniffReceived(p, c);
}
}

View File

@@ -27,10 +27,6 @@
*/
class FloodingRouter : public Router
{
private:
/* Check if we should rebroadcast this packet, and do so if needed */
void perhapsRebroadcast(const meshtastic_MeshPacket *p);
public:
/**
* Constructor
@@ -59,7 +55,18 @@ class FloodingRouter : public Router
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
// Return false for roles like ROUTER or REPEATER which should always rebroadcast even when we've heard another rebroadcast of
/* Check if we should rebroadcast this packet, and do so if needed */
virtual bool perhapsRebroadcast(const meshtastic_MeshPacket *p) = 0;
/* Check if we should handle an upgraded packet (with higher hop_limit)
* @return true if we handled it (so stop processing)
*/
bool perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p);
/* Call when we receive a packet that needs some reprocessing, but afterwards should be filtered */
void reprocessPacket(const meshtastic_MeshPacket *p);
// Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of
// the same packet
bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);

View File

@@ -155,7 +155,7 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
err = lora.setBandwidth(bw);
err = lora.setBandwidth(bw, wideLora() && (getFreq() > 1000.0f));
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);

View File

@@ -65,5 +65,7 @@ template <class T> class LR11x0Interface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
};
#endif

View File

@@ -65,7 +65,7 @@ void fixPriority(meshtastic_MeshPacket *p)
}
/** enqueue a packet, return false if full */
bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p)
bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p, bool *dropped)
{
// no space - try to replace a lower priority packet in the queue
if (queue.size() >= maxLen) {
@@ -73,9 +73,16 @@ bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p)
if (!replaced) {
LOG_WARN("TX queue is full, and there is no lower-priority packet available to evict in favour of 0x%08x", p->id);
}
if (dropped) {
*dropped = true;
}
return replaced;
}
if (dropped) {
*dropped = false;
}
// Find the correct position using upper_bound to maintain a stable order
auto it = std::upper_bound(queue.begin(), queue.end(), p, CompareMeshPacketFunc);
queue.insert(it, p); // Insert packet at the found position
@@ -103,12 +110,26 @@ meshtastic_MeshPacket *MeshPacketQueue::getFront()
return p;
}
/** Attempt to find and remove a packet from this queue. Returns a pointer to the removed packet, or NULL if not found */
meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool tx_normal, bool tx_late)
/** Get a packet from this queue. Returns a pointer to the packet, or NULL if not found. */
meshtastic_MeshPacket *MeshPacketQueue::getPacketFromQueue(NodeNum from, PacketId id)
{
for (auto it = queue.begin(); it != queue.end(); it++) {
auto p = (*it);
if (getFrom(p) == from && p->id == id && ((tx_normal && !p->tx_after) || (tx_late && p->tx_after))) {
if (getFrom(p) == from && p->id == id) {
return p;
}
}
return NULL;
}
/** Attempt to find and remove a packet from this queue. Returns a pointer to the removed packet, or NULL if not found */
meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool tx_normal, bool tx_late, uint8_t hop_limit_lt)
{
for (auto it = queue.begin(); it != queue.end(); it++) {
auto p = (*it);
if (getFrom(p) == from && p->id == id && ((tx_normal && !p->tx_after) || (tx_late && p->tx_after)) &&
(!hop_limit_lt || p->hop_limit < hop_limit_lt)) {
queue.erase(it);
return p;
}
@@ -120,14 +141,7 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool t
/* Attempt to find a packet from this queue. Return true if it was found. */
bool MeshPacketQueue::find(const NodeNum from, const PacketId id)
{
for (auto it = queue.begin(); it != queue.end(); it++) {
const auto *p = *it;
if (getFrom(p) == from && p->id == id) {
return true;
}
}
return false;
return getPacketFromQueue(from, id) != NULL;
}
/**

View File

@@ -19,8 +19,10 @@ class MeshPacketQueue
public:
explicit MeshPacketQueue(size_t _maxLen);
/** enqueue a packet, return false if full */
bool enqueue(meshtastic_MeshPacket *p);
/** enqueue a packet, return false if full
* @param dropped Optional pointer to a bool that will be set to true if a packet was dropped
*/
bool enqueue(meshtastic_MeshPacket *p, bool *dropped = nullptr);
/** return true if the queue is empty */
bool empty();
@@ -35,8 +37,12 @@ class MeshPacketQueue
meshtastic_MeshPacket *getFront();
/** Get a packet from this queue. Returns a pointer to the packet, or NULL if not found. */
meshtastic_MeshPacket *getPacketFromQueue(NodeNum from, PacketId id);
/** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */
meshtastic_MeshPacket *remove(NodeNum from, PacketId id, bool tx_normal = true, bool tx_late = true);
meshtastic_MeshPacket *remove(NodeNum from, PacketId id, bool tx_normal = true, bool tx_late = true,
uint8_t hop_limit_lt = 0);
/* Attempt to find a packet from this queue. Return true if it was found. */
bool find(const NodeNum from, const PacketId id);

View File

@@ -85,12 +85,11 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping
nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
bool isPreferredRebroadcaster =
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_REPEATER);
bool isPreferredRebroadcaster = config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER;
if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo"); // because this potentially a Repeater which will
// ignore our request for its NodeInfo
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo");
// ignore our request for its NodeInfo
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) {
if (airTime->isTxAllowedChannelUtil(true)) {
@@ -453,4 +452,4 @@ uint32_t MeshService::GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp)
delta = 0;
return delta;
}
}

View File

@@ -190,4 +190,4 @@ class MeshService
friend class RoutingModule;
};
extern MeshService *service;
extern MeshService *service;

View File

@@ -1,4 +1,10 @@
#include "NextHopRouter.h"
#include "MeshTypes.h"
#include "meshUtils.h"
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
#include "modules/TraceRouteModule.h"
#endif
#include "NodeDB.h"
NextHopRouter::NextHopRouter() {}
@@ -32,7 +38,16 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
{
bool wasFallback = false;
bool weWereNextHop = false;
if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
bool wasUpgraded = false;
bool seenRecently = wasSeenRecently(p, true, &wasFallback, &weWereNextHop,
&wasUpgraded); // Updates history; returns false when an upgrade is detected
// Handle hop_limit upgrade scenario for rebroadcasters
if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
return true; // we handled it, so stop processing
}
if (seenRecently) {
printPacket("Ignore dupe incoming msg", p);
if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
@@ -44,14 +59,20 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
if (wasFallback) {
LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
// Check if it's still in the Tx queue, if not, we have to relay it again
if (!findInTxQueue(p->from, p->id))
perhapsRelay(p);
if (!findInTxQueue(p->from, p->id)) {
reprocessPacket(p);
perhapsRebroadcast(p);
}
} else {
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
if (isRepeated) {
if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
if (!findInTxQueue(p->from, p->id)) {
reprocessPacket(p);
if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
}
}
} else if (!weWereNextHop) {
perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
}
@@ -69,18 +90,22 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
if (isAckorReply) {
// Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
// not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
// Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from"
// is not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the
// destination
if (p->from != 0) {
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
if (origTx) {
// Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came directly
// from the destination
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
(p->hop_start != 0 && p->hop_start == p->hop_limit &&
wasSoleRelayer(ourRelayID, p->decoded.request_id, p->to))) {
// Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came
// directly from the destination
bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
bool weWereSoleRelayer = false;
bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
if ((weWereRelayer && wasAlreadyRelayer) ||
(p->hop_start != 0 && p->hop_start == p->hop_limit && weWereSoleRelayer)) {
if (origTx->next_hop != p->relay_node) { // Not already set
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply (was relayer %d we were sole %d)", p->from,
p->relay_node, wasAlreadyRelayer, weWereSoleRelayer);
origTx->next_hop = p->relay_node;
}
}
@@ -93,28 +118,49 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
}
}
perhapsRelay(p);
perhapsRebroadcast(p);
// handle the packet as normal
Router::sniffReceived(p, c);
}
/* Check if we should be relaying this packet if so, do so. */
bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
/* Check if we should be rebroadcasting this packet if so, do so. */
bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
{
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
if (p->id != 0) {
if (isRebroadcaster()) {
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
LOG_INFO("Relaying received message coming from %x", p->relay_node);
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
LOG_INFO("Rebroadcast received message coming from %x", p->relay_node);
tosend->hop_limit--; // bump down the hop count
NextHopRouter::send(tosend);
// Use shared logic to determine if hop_limit should be decremented
if (shouldDecrementHopLimit(p)) {
tosend->hop_limit--; // bump down the hop count
} else {
LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
}
#if USERPREFS_EVENT_MODE
if (tosend->hop_limit > 2) {
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
tosend->hop_start -= (tosend->hop_limit - 2);
tosend->hop_limit = 2;
}
#endif
return true;
if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {
FloodingRouter::send(tosend);
} else {
NextHopRouter::send(tosend);
}
return true;
}
} else {
LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
}
} else {
LOG_DEBUG("Ignore 0 id broadcast");
}
}
@@ -127,7 +173,6 @@ bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
*/
uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
{
// When we're a repeater router->sniffReceived will call NextHopRouter directly without checking for broadcast
if (isBroadcast(to))
return NO_NEXT_HOP_PREFERENCE;
@@ -165,7 +210,7 @@ bool NextHopRouter::roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *
{
// Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
// Return false for roles like ROUTER, REPEATER, ROUTER_LATE which should always transmit the packet at least once.
// Return false for roles like ROUTER, ROUTER_LATE which should always transmit the packet at least once.
return roleAllowsCancelingDupe(p); // same logic as FloodingRouter::roleAllowsCancelingDupe
}
@@ -178,20 +223,20 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
to avoid canceling a transmission if it was ACKed super fast via MQTT */
if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
// We only cancel it if we are the original sender or if we're not a router(_late)/repeater
// We only cancel it if we are the original sender or if we're not a router(_late)
if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id);
}
}
// Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
// get scheduled again. (This is the core of stopRetransmission.)
// Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it
// doesn't get scheduled again. (This is the core of stopRetransmission.)
auto numErased = pending.erase(key);
assert(numErased == 1);
// When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
// to startRetransmission.
// When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the
// call to startRetransmission.
packetPool.release(p);
return true;
@@ -291,4 +336,4 @@ void NextHopRouter::setNextTx(PendingPacket *pending)
LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
printPacket("", pending->packet);
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
}
}

View File

@@ -148,7 +148,7 @@ class NextHopRouter : public FloodingRouter
*/
uint8_t getNextHop(NodeNum to, uint8_t relay_node);
/** Check if we should be relaying this packet if so, do so.
* @return true if we did relay */
bool perhapsRelay(const meshtastic_MeshPacket *p);
/** Check if we should be rebroadcasting this packet if so, do so.
* @return true if we did rebroadcast */
bool perhapsRebroadcast(const meshtastic_MeshPacket *p) override;
};

View File

@@ -204,7 +204,7 @@ NodeDB::NodeDB()
int saveWhat = 0;
// Get device unique id
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
uint32_t unique_id[4];
// ESP32 factory burns a unique id in efuse for S2+ series and evidently C3+ series
// This is used for HMACs in the esp-rainmaker AIOT platform and seems to be a good choice for us
@@ -256,6 +256,8 @@ NodeDB::NodeDB()
owner.role = config.device.role;
// Ensure macaddr is set to our macaddr as it will be copied in our info below
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
// Ensure owner.id is always derived from the node number
snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum());
if (!config.has_security) {
config.has_security = true;
@@ -554,10 +556,9 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#endif
#ifdef USERPREFS_CONFIG_DEVICE_ROLE
// Restrict ROUTER*, LOST AND FOUND, and REPEATER roles for security reasons
// Restrict ROUTER*, LOST AND FOUND roles for security reasons
if (IS_ONE_OF(USERPREFS_CONFIG_DEVICE_ROLE, meshtastic_Config_DeviceConfig_Role_ROUTER,
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_REPEATER,
meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND)) {
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND)) {
LOG_WARN("ROUTER roles are restricted, falling back to CLIENT role");
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
} else {
@@ -701,7 +702,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#ifdef USERPREFS_NETWORK_ENABLED_PROTOCOLS
config.network.enabled_protocols = USERPREFS_NETWORK_ENABLED_PROTOCOLS;
#else
config.network.enabled_protocols = 1;
config.network.enabled_protocols = 0;
#endif
#endif
@@ -906,11 +907,6 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
moduleConfig.telemetry.device_update_interval = ONE_DAY;
owner.has_is_unmessagable = true;
owner.is_unmessagable = true;
} else if (role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
owner.has_is_unmessagable = true;
owner.is_unmessagable = true;
config.display.screen_on_secs = 1;
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY;
} else if (role == meshtastic_Config_DeviceConfig_Role_SENSOR) {
owner.has_is_unmessagable = true;
owner.is_unmessagable = true;
@@ -1158,6 +1154,20 @@ void NodeDB::loadFromDisk()
spiLock->unlock();
#endif
#ifdef FSCom
#ifdef FACTORY_INSTALL
spiLock->lock();
if (!FSCom.exists("/prefs/" xstr(BUILD_EPOCH))) {
LOG_WARN("Factory Install Reset!");
FSCom.format();
FSCom.mkdir("/prefs");
File f2 = FSCom.open("/prefs/" xstr(BUILD_EPOCH), FILE_O_WRITE);
if (f2) {
f2.flush();
f2.close();
}
}
spiLock->unlock();
#endif
spiLock->lock();
if (FSCom.exists(legacyPrefFileName)) {
spiLock->unlock();
@@ -1603,9 +1613,18 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
void NodeDB::addFromContact(meshtastic_SharedContact contact)
{
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
if (!info) {
if (!info || !contact.has_user) {
return;
}
// If the local node has this node marked as manually verified
// and the client does not, do not allow the client to update the
// saved public key.
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
if (contact.user.public_key.size != info->user.public_key.size ||
memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
return;
}
}
info->num = contact.node_num;
info->has_user = true;
info->user = TypeConversions::ConvertToUserLite(contact.user);
@@ -1620,10 +1639,12 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
} else {
info->last_heard = getValidTime(RTCQualityNTP);
info->is_favorite = true;
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
// As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
if (contact.manually_verified) {
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
// Mark the node's key as manually verified to indicate trustworthiness.
updateGUIforNode = info;
// powerFSM.trigger(EVENT_NODEDB_UPDATED); This event has been retired
sortMeshDB();
notifyObservers(true); // Force an update whether or not our node counts have changed
}
@@ -1667,14 +1688,14 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
return false;
}
LOG_INFO("Public Key set for node, not updating!");
// we copy the key into the incoming packet, to prevent overwrite
p.public_key.size = 32;
memcpy(p.public_key.bytes, info->user.public_key.bytes, 32);
} else if (p.public_key.size == 32) {
LOG_INFO("Update Node Pubkey!");
}
#endif
// Always ensure user.id is derived from nodeId, regardless of what was received
snprintf(p.id, sizeof(p.id), "!%08x", nodeId);
// Both of info->user and p start as filled with zero so I think this is okay
auto lite = TypeConversions::ConvertToUserLite(p);
bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex);

View File

@@ -45,7 +45,8 @@ PacketHistory::~PacketHistory()
}
/** Update recentPackets and return true if we have already seen this packet */
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate, bool *wasFallback, bool *weWereNextHop)
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate, bool *wasFallback, bool *weWereNextHop,
bool *wasUpgraded)
{
if (!initOk()) {
LOG_ERROR("Packet History - Was Seen Recently: NOT INITIALIZED!");
@@ -66,7 +67,14 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
r.id = p->id;
r.sender = getFrom(p); // If 0 then use our ID
r.next_hop = p->next_hop;
r.relayed_by[0] = p->relay_node;
setHighestHopLimit(r, p->hop_limit);
bool weWillRelay = false;
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum());
if (p->relay_node == ourRelayID) { // If the relay_node is us, store it
weWillRelay = true;
setOurTxHopLimit(r, p->hop_limit);
r.relayed_by[0] = p->relay_node;
}
r.rxTimeMsec = millis(); //
if (r.rxTimeMsec == 0) // =0 every 49.7 days? 0 is special
@@ -81,9 +89,16 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
PacketRecord *found = find(r.sender, r.id); // Find the packet record in the recentPackets array
bool seenRecently = (found != NULL); // If found -> the packet was seen recently
if (seenRecently) {
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum()); // Get our relay ID from our node number
// Check for hop_limit upgrade scenario
if (seenRecently && wasUpgraded && found->hop_limit < p->hop_limit) {
LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit,
p->hop_limit);
*wasUpgraded = true;
} else if (wasUpgraded) {
*wasUpgraded = false; // Initialize to false if not an upgrade
}
if (seenRecently) {
if (wasFallback) {
// If it was seen with a next-hop not set to us and now it's NO_NEXT_HOP_PREFERENCE, and the relayer relayed already
// before, it's a fallback to flooding. If we didn't already relay and the next-hop neither, we might need to handle
@@ -125,11 +140,40 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
found->sender, found->id, found->next_hop, found->relayed_by[0], found->relayed_by[1], found->relayed_by[2],
millis() - found->rxTimeMsec);
#endif
// Only update the relayer if it heard us directly (meaning hopLimit is decreased by 1)
uint8_t startIdx = weWillRelay ? 1 : 0;
if (!weWillRelay) {
bool weWereRelayer = wasRelayer(ourRelayID, *found);
// We were a relayer and the packet came in with a hop limit that is one less than when we sent it out
if (weWereRelayer && (p->hop_limit == getOurTxHopLimit(*found) || p->hop_limit == getOurTxHopLimit(*found) - 1)) {
r.relayed_by[0] = p->relay_node;
startIdx = 1; // Start copying existing relayers from index 1
}
// keep the original ourTxHopLimit
setOurTxHopLimit(r, getOurTxHopLimit(*found));
}
// Add the existing relayed_by to the new record
for (uint8_t i = 0; i < (NUM_RELAYERS - 1); i++) {
if (found->relayed_by[i] != 0)
r.relayed_by[i + 1] = found->relayed_by[i];
// Preserve the highest hop_limit we've ever seen for this packet so upgrades aren't lost when a later copy has
// fewer hops remaining.
if (getHighestHopLimit(*found) > getHighestHopLimit(r))
setHighestHopLimit(r, getHighestHopLimit(*found));
// Add the existing relayed_by to the new record, avoiding duplicates
for (uint8_t i = 0; i < (NUM_RELAYERS - startIdx); i++) {
if (found->relayed_by[i] == 0)
continue;
bool exists = false;
for (uint8_t j = 0; j < NUM_RELAYERS; j++) {
if (r.relayed_by[j] == found->relayed_by[i]) {
exists = true;
break;
}
}
if (!exists) {
r.relayed_by[i + startIdx] = found->relayed_by[i];
}
}
r.next_hop = found->next_hop; // keep the original next_hop (such that we check whether we were originally asked)
#if VERBOSE_PACKET_HISTORY
@@ -352,14 +396,6 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r, boo
return found;
}
// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
bool PacketHistory::wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
bool wasSole = false;
wasRelayer(relayer, id, sender, &wasSole);
return wasSole;
}
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
@@ -401,3 +437,24 @@ void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, cons
found->id, found->relayed_by[0], found->relayed_by[1], found->relayed_by[2], relayer, i != j);
#endif
}
// Getters and setters for hop limit fields packed in hop_limit
inline uint8_t PacketHistory::getHighestHopLimit(PacketRecord &r)
{
return r.hop_limit & HOP_LIMIT_HIGHEST_MASK;
}
inline void PacketHistory::setHighestHopLimit(PacketRecord &r, uint8_t hopLimit)
{
r.hop_limit = (r.hop_limit & ~HOP_LIMIT_HIGHEST_MASK) | (hopLimit & HOP_LIMIT_HIGHEST_MASK);
}
inline uint8_t PacketHistory::getOurTxHopLimit(PacketRecord &r)
{
return (r.hop_limit & HOP_LIMIT_OUR_TX_MASK) >> HOP_LIMIT_OUR_TX_SHIFT;
}
inline void PacketHistory::setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit)
{
r.hop_limit = (r.hop_limit & ~HOP_LIMIT_OUR_TX_MASK) | ((hopLimit << HOP_LIMIT_OUR_TX_SHIFT) & HOP_LIMIT_OUR_TX_MASK);
}

View File

@@ -2,8 +2,11 @@
#include "NodeDB.h"
#define NUM_RELAYERS \
3 // Number of relayer we keep track of. Use 3 to be efficient with memory alignment of PacketRecord to 16 bytes
// Number of relayers we keep track of. Use 6 to be efficient with memory alignment of PacketRecord to 20 bytes
#define NUM_RELAYERS 6
#define HOP_LIMIT_HIGHEST_MASK 0x07 // Bits 0-2
#define HOP_LIMIT_OUR_TX_MASK 0x38 // Bits 3-5
#define HOP_LIMIT_OUR_TX_SHIFT 3 // Bits 3-5
/**
* This is a mixin that adds a record of past packets we have seen
@@ -16,8 +19,10 @@ class PacketHistory
PacketId id;
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it, 0 means empty
uint8_t next_hop; // The next hop asked for this packet
uint8_t hop_limit; // bit 0-2: Highest hop limit observed for this packet,
// bit 3-5: our hop limit when we first transmitted it
uint8_t relayed_by[NUM_RELAYERS]; // Array of nodes that relayed this packet
}; // 4B + 4B + 4B + 1B + 3B = 16B
}; // 4B + 4B + 4B + 1B + 1B + 6B = 20B
uint32_t recentPacketsCapacity =
0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets.
@@ -38,6 +43,11 @@ class PacketHistory
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole = nullptr);
uint8_t getHighestHopLimit(PacketRecord &r);
void setHighestHopLimit(PacketRecord &r, uint8_t hopLimit);
uint8_t getOurTxHopLimit(PacketRecord &r);
void setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit);
PacketHistory(const PacketHistory &); // non construction-copyable
PacketHistory &operator=(const PacketHistory &); // non copyable
public:
@@ -50,18 +60,16 @@ class PacketHistory
* @param withUpdate if true and not found we add an entry to recentPackets
* @param wasFallback if not nullptr, packet will be checked for fallback to flooding and value will be set to true if so
* @param weWereNextHop if not nullptr, packet will be checked for us being the next hop and value will be set to true if so
* @param wasUpgraded if not nullptr, will be set to true if this packet has better hop_limit than previously seen
*/
bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true, bool *wasFallback = nullptr,
bool *weWereNextHop = nullptr);
bool *weWereNextHop = nullptr, bool *wasUpgraded = nullptr);
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
* If wasSole is not nullptr, it will be set to true if the relayer was the only relayer of that packet
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole = nullptr);
// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
bool wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);

View File

@@ -250,6 +250,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
// If client only wants node info, jump directly to sending nodes
state = STATE_SEND_OTHER_NODEINFOS;
onNowHasData(0);
} else {
state = STATE_SEND_METADATA;
}
@@ -423,6 +424,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
state = STATE_SEND_FILEMANIFEST;
} else {
state = STATE_SEND_OTHER_NODEINFOS;
onNowHasData(0);
}
config_state = 0;
}
@@ -588,6 +590,7 @@ bool PhoneAPI::available()
nodeInfoForPhone.snr = isUs ? 0 : nodeInfoForPhone.snr;
nodeInfoForPhone.via_mqtt = isUs ? false : nodeInfoForPhone.via_mqtt;
nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || isUs; // Our node is always a favorite
onNowHasData(0);
}
}
return true; // Always say we have something, because we might need to advance our state machine
@@ -707,6 +710,13 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
// sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Text messages can only be sent once every 2 seconds");
return false;
}
// Upgrade traceroute requests from phone to use reliable delivery, matching TraceRouteModule
if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && !isBroadcast(p.to)) {
// Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)
p.want_ack = true;
}
lastPortNumToRadio[p.decoded.portnum] = millis();
service->handleToRadio(p);
return true;

View File

@@ -65,8 +65,10 @@ class RF95Interface : public RadioLibInterface
*/
virtual void configHardwareForSend() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(*lora, pl, received); }
private:
/** Some boards require GPIO control of tx vs rx paths */
void setTransmitEnable(bool txon);
};
#endif
#endif

View File

@@ -230,33 +230,7 @@ The band is from 902 to 928 MHz. It mentions channel number and its respective c
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
*/
/**
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
* section 4
*
* @return num msecs for the packet
*/
uint32_t RadioInterface::getPacketTime(uint32_t pl)
{
float bandwidthHz = bw * 1000.0f;
bool headDisable = false; // we currently always use the header
float tSym = (1 << sf) / bandwidthHz;
bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
float tPreamble = (preambleLength + 4.25f) * tSym;
float numPayloadSym =
8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
float tPayload = numPayloadSym * tSym;
float tPacket = tPreamble + tPayload;
uint32_t msecs = tPacket * 1000;
return msecs;
}
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p)
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received)
{
uint32_t pl = 0;
if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
@@ -265,7 +239,7 @@ uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p)
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
pl = numbytes + sizeof(PacketHeader);
}
return getPacketTime(pl);
return getPacketTime(pl, received);
}
/** The delay to use for retransmitting dropped packets */
@@ -317,9 +291,8 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
/** Returns true if we should rebroadcast early like a ROUTER */
bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
{
// If we are a ROUTER or REPEATER, we always rebroadcast early
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
// If we are a ROUTER, we always rebroadcast early
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
return true;
}
@@ -625,8 +598,7 @@ void RadioInterface::applyModemConfig()
saveFreq(freq + loraConfig.frequency_offset);
slotTimeMsec = computeSlotTimeMsec();
preambleTimeMsec = getPacketTime((uint32_t)0);
maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset);
LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset,
@@ -636,7 +608,7 @@ void RadioInterface::applyModemConfig()
LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw);
LOG_INFO("channel_num: %d", channel_num + 1);
LOG_INFO("frequency: %f", getFreq());
LOG_INFO("Slot time: %u msec", slotTimeMsec);
LOG_INFO("Slot time: %u msec, preamble time: %u msec", slotTimeMsec, preambleTimeMsec);
}
/** Slottime is the time to detect a transmission has started, consisting of:
@@ -674,11 +646,25 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
power = maxPower;
}
#ifndef NUM_PA_POINTS
if (TX_GAIN_LORA > 0) {
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA);
power -= TX_GAIN_LORA;
}
#else
// we have an array of PA gain values. Find the highest power setting that works.
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
// we've exceeded the power limit, or hit the max we can do
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
power -= tx_gain[radio_dbm];
break;
}
}
#endif
if (power > loraMaxPower) // Clamp power to maximum defined level
power = loraMaxPower;

View File

@@ -87,9 +87,8 @@ class RadioInterface
const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
uint32_t slotTimeMsec = computeSlotTimeMsec();
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
const uint32_t PROCESSING_TIME_MSEC =
4500; // time to construct, process and construct a packet again (empirically determined)
const uint8_t CWmin = 3; // minimum CWsize
@@ -189,6 +188,12 @@ class RadioInterface
/** If the packet is not already in the late rebroadcast window, move it there */
virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }
/**
* If there is a packet pending TX in the queue with a worse hop limit, remove it pending replacement with a better version
* @return Whether a pending packet was removed
*/
virtual bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) { return false; }
/**
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
@@ -196,8 +201,8 @@ class RadioInterface
*
* @return num msecs for the packet
*/
uint32_t getPacketTime(const meshtastic_MeshPacket *p);
uint32_t getPacketTime(uint32_t totalPacketLen);
uint32_t getPacketTime(const meshtastic_MeshPacket *p, bool received = false);
virtual uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) = 0;
/**
* Get the channel we saved.
@@ -266,4 +271,4 @@ class RadioInterface
};
/// Debug printing for packets
void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
void printPacket(const char *prefix, const meshtastic_MeshPacket *p);

View File

@@ -116,16 +116,21 @@ bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidF
if (detected) {
if (!activeReceiveStart) {
activeReceiveStart = millis();
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && !(irq & syncWordHeaderValidFlag)) {
// The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
activeReceiveStart = 0;
LOG_DEBUG("Ignore false preamble detection");
return false;
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
// We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
activeReceiveStart = 0;
LOG_DEBUG("Ignore false header detection");
return false;
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec)) {
if (!(irq & syncWordHeaderValidFlag)) {
// The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
activeReceiveStart = 0;
LOG_DEBUG("Ignore false preamble detection");
return false;
} else {
uint32_t maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
// We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
activeReceiveStart = 0;
LOG_DEBUG("Ignore false header detection");
return false;
}
}
}
}
return detected;
@@ -172,7 +177,12 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p)
printPacket("enqueue for send", p);
LOG_DEBUG("txGood=%d,txRelay=%d,rxGood=%d,rxBad=%d", txGood, txRelay, rxGood, rxBad);
ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN;
bool dropped = false;
ErrorCode res = txQueue.enqueue(p, &dropped) ? ERRNO_OK : ERRNO_UNKNOWN;
if (dropped) {
txDrop++;
}
if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
packetPool.release(p);
@@ -354,14 +364,38 @@ void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id)
meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false);
if (p) {
p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr);
if (txQueue.enqueue(p)) {
bool dropped = false;
if (txQueue.enqueue(p, &dropped)) {
LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
} else {
packetPool.release(p);
}
if (dropped) {
txDrop++;
}
}
}
/**
* If there is a packet pending TX in the queue with a worse hop limit, remove it pending replacement with a better version
* @return Whether a pending packet was removed
*/
bool RadioLibInterface::removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt)
{
meshtastic_MeshPacket *p = txQueue.remove(from, id, true, true, hop_limit_lt);
if (p) {
LOG_DEBUG("Dropping pending-TX packet 0x%08x with hop limit %d", p->id, p->hop_limit);
packetPool.release(p);
return true;
}
return false;
}
/**
* Remove a packet that is eligible for replacement from the TX queue
*/
// void RadioLibInterface::removePending
void RadioLibInterface::handleTransmitInterrupt()
{
// This can be null if we forced the device to enter standby mode. In that case
@@ -391,8 +425,6 @@ void RadioLibInterface::completeSending()
void RadioLibInterface::handleReceiveInterrupt()
{
uint32_t xmitMsec;
// when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race
// Condition?
if (!isReceiving) {
@@ -405,12 +437,12 @@ void RadioLibInterface::handleReceiveInterrupt()
// read the number of actually received bytes
size_t length = iface->getPacketLength();
xmitMsec = getPacketTime(length);
uint32_t rxMsec = getPacketTime(length, true);
#ifndef DISABLE_WELCOME_UNSET
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
LOG_WARN("lora rx disabled: Region unset");
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
airTime->logAirtime(RX_ALL_LOG, rxMsec);
return;
}
#endif
@@ -426,7 +458,7 @@ void RadioLibInterface::handleReceiveInterrupt()
radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
airTime->logAirtime(RX_ALL_LOG, rxMsec);
} else {
// Skip the 4 headers that are at the beginning of the rxBuf
@@ -436,7 +468,7 @@ void RadioLibInterface::handleReceiveInterrupt()
if (payloadLen < 0) {
LOG_WARN("Ignore received packet too short");
rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
airTime->logAirtime(RX_ALL_LOG, rxMsec);
} else {
rxGood++;
// altered packet with "from == 0" can do Remote Node Administration without permission
@@ -474,7 +506,7 @@ void RadioLibInterface::handleReceiveInterrupt()
printPacket("Lora RX", mp);
airTime->logAirtime(RX_LOG, xmitMsec);
airTime->logAirtime(RX_LOG, rxMsec);
deliverToReceiver(mp);
}

View File

@@ -61,6 +61,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE);
protected:
ModemType_t modemType = RADIOLIB_MODEM_LORA;
DataRate_t getDataRate() const { return {.lora = {.spreadingFactor = sf, .bandwidth = bw, .codingRate = cr}}; }
PacketConfig_t getPacketConfig() const
{
return {.lora = {.preambleLength = preambleLength,
.implicitHeader = false,
.crcEnabled = true,
// We use auto LDRO, meaning it is enabled if the symbol time is >= 16msec
.ldrOptimize = (1 << sf) / bw >= 16}};
}
/**
* We use a meshtastic sync word, but hashed with the Channel name. For releases before 1.2 we used 0x12 (or for very old
* loads 0x14) Note: do not use 0x34 - that is reserved for lorawan
@@ -105,6 +116,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
* Debugging counts
*/
uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0;
uint16_t txDrop = 0;
public:
RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
@@ -209,10 +221,47 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
*/
virtual void setStandby();
/**
* Derive packet time either for a received (using header info) or a transmitted packet
*/
template <typename T> uint32_t computePacketTime(T &lora, uint32_t pl, bool received)
{
if (received) {
// First get the actual coding rate and CRC status from the received packet
uint8_t rxCR;
bool hasCRC;
lora.getLoRaRxHeaderInfo(&rxCR, &hasCRC);
// Go from raw header value to denominator
if (rxCR < 5) {
rxCR += 4;
} else if (rxCR == 7) {
rxCR = 8;
}
// Received packet configuration must be the same as configured, except for coding rate and CRC
DataRate_t dr = getDataRate();
dr.lora.codingRate = rxCR;
PacketConfig_t pc = getPacketConfig();
pc.lora.crcEnabled = hasCRC;
return lora.calculateTimeOnAir(modemType, dr, pc, pl) / 1000;
}
return lora.getTimeOnAir(pl) / 1000;
}
const char *radioLibErr = "RadioLib err=";
/**
* If the packet is not already in the late rebroadcast window, move it there
*/
void clampToLateRebroadcastWindow(NodeNum from, PacketId id);
};
/**
* If there is a packet pending TX in the queue with a worse hop limit, remove it pending replacement with a better version
* @return Whether a pending packet was removed
*/
bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override;
};

View File

@@ -76,7 +76,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
If we don't add this, we will likely retransmit too early.
*/
for (auto i = pending.begin(); i != pending.end(); i++) {
i->second.nextTxMsec += iface->getPacketTime(p);
i->second.nextTxMsec += iface->getPacketTime(p, true);
}
return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p);
@@ -103,10 +103,20 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
/* A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received
an implicit ACK already. If we received it directly or via NextHopRouter, only ACK with a hop limit of 0 to
make sure the other side stops retransmitting. */
if (!p->decoded.request_id && !p->decoded.reply_id) {
if (shouldSuccessAckWithWantAck(p)) {
// If this packet should always be ACKed reliably with want_ack back to the original sender, make sure we
// do that unconditionally.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit), true);
} else if (!p->decoded.request_id && !p->decoded.reply_id) {
// If it's not an ACK or a reply, send an ACK.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
} else if ((p->hop_start > 0 && p->hop_start == p->hop_limit) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
// If we received the packet directly from the original sender, send a 0-hop ACK since the original sender
// won't overhear any implicit ACKs. If we received the packet via NextHopRouter, also send a 0-hop ACK to
// stop the immediate relayer's retransmissions.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
}
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
@@ -152,4 +162,36 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
// handle the packet as normal
isBroadcast(p->to) ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c);
}
/**
* If we ACK this packet, should we set want_ack=true on the ACK for reliable delivery of the ACK packet?
*/
bool ReliableRouter::shouldSuccessAckWithWantAck(const meshtastic_MeshPacket *p)
{
// Don't ACK-with-want-ACK outgoing packets
if (isFromUs(p))
return false;
// Only ACK-with-want-ACK if the original packet asked for want_ack
if (!p->want_ack)
return false;
// Only ACK-with-want-ACK packets to us (not broadcast)
if (!isToUs(p))
return false;
// Special case for text message DMs:
bool isTextMessage =
(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_TEXT_MESSAGE_APP, meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP);
if (isTextMessage) {
// If it's a non-broadcast text message, and the original asked for want_ack,
// let's send an ACK that is itself want_ack to improve reliability of confirming delivery back to the sender.
// This should include all DMs regardless of whether or not reply_id is set.
return true;
}
return false;
}

View File

@@ -31,4 +31,10 @@ class ReliableRouter : public NextHopRouter
* We hook this method so we can see packets before FloodingRouter says they should be discarded
*/
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
private:
/**
* Should this packet be ACKed with a want_ack for reliable delivery?
*/
bool shouldSuccessAckWithWantAck(const meshtastic_MeshPacket *p);
};

View File

@@ -69,6 +69,58 @@ Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRA
cryptLock = new concurrency::Lock();
}
bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
{
// First hop MUST always decrement to prevent retry issues
bool isFirstHop = (p->hop_start != 0 && p->hop_start == p->hop_limit);
if (isFirstHop) {
return true; // Always decrement on first hop
}
// Check if both local device and previous relay are routers (including CLIENT_BASE)
bool localIsRouter =
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE,
meshtastic_Config_DeviceConfig_Role_CLIENT_BASE);
// If local device isn't a router, always decrement
if (!localIsRouter) {
return true;
}
// For subsequent hops, check if previous relay is a favorite router
// Optimized search for favorite routers with matching last byte
// Check ordering optimized for IoT devices (cheapest checks first)
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
if (!node)
continue;
// Check 1: is_favorite (cheapest - single bool)
if (!node->is_favorite)
continue;
// Check 2: has_user (cheap - single bool)
if (!node->has_user)
continue;
// 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)) {
continue;
}
// Check 4: last byte extraction and comparison (most expensive)
if (nodeDB->getLastByteOfNodeNum(node->num) == p->relay_node) {
// Found a favorite router match
LOG_DEBUG("Identified favorite relay router 0x%x from last byte 0x%x", node->num, p->relay_node);
return false; // Don't decrement hop_limit
}
}
// No favorite router match found, decrement hop_limit
return true;
}
/**
* do idle processing
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
@@ -146,9 +198,10 @@ meshtastic_MeshPacket *Router::allocForSending()
/**
* Send an ack or a nak packet back towards whoever sent idFrom
*/
void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit)
void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit,
bool ackWantsAck)
{
routingModule->sendAckNak(err, to, idFrom, chIndex, hopLimit);
routingModule->sendAckNak(err, to, idFrom, chIndex, hopLimit, ackWantsAck);
}
void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p)
@@ -347,10 +400,6 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
{
concurrency::LockGuard g(cryptLock);
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_ALL_SKIP_DECODING)
return DecodeState::DECODE_FAILURE;
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
(nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) {
LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet", p->from);
@@ -431,35 +480,6 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
}
}
#if HAS_UDP_MULTICAST
// Fallback: for UDP multicast, try default preset names with default PSK if normal channel match failed
if (!decrypted && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP) {
if (channels.setDefaultPresetCryptoForHash(p->channel)) {
memcpy(bytes, p->encrypted.bytes, rawSize);
crypto->decrypt(p->from, p->id, rawSize, bytes);
meshtastic_Data decodedtmp;
memset(&decodedtmp, 0, sizeof(decodedtmp));
if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) &&
decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) {
p->decoded = decodedtmp;
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
// Map to our local default channel index (name+PSK default), not necessarily primary
ChannelIndex defaultIndex = channels.getPrimaryIndex();
for (ChannelIndex i = 0; i < channels.getNumChannels(); ++i) {
if (channels.isDefaultChannel(i)) {
defaultIndex = i;
break;
}
}
chIndex = defaultIndex;
decrypted = true;
} else {
LOG_WARN("UDP fallback decode attempted but failed for hash 0x%x", p->channel);
}
}
}
#endif
if (decrypted) {
// parsing was successful
p->channel = chIndex; // change to store the index instead of the hash

View File

@@ -104,6 +104,18 @@ class Router : protected concurrency::OSThread, protected PacketHistory
*/
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) { return false; }
/**
* Determine if hop_limit should be decremented for a relay operation.
* Returns false (preserve hop_limit) only if all conditions are met:
* - It's NOT the first hop (first hop must always decrement)
* - Local device is a ROUTER, ROUTER_LATE, or CLIENT_BASE
* - Previous relay is a favorite ROUTER, ROUTER_LATE, or CLIENT_BASE
*
* @param p The packet being relayed
* @return true if hop_limit should be decremented, false to preserve it
*/
bool shouldDecrementHopLimit(const meshtastic_MeshPacket *p);
/**
* Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to
* update routing tables etc... based on what we overhear (even for messages not destined to our node)
@@ -113,7 +125,8 @@ class Router : protected concurrency::OSThread, protected PacketHistory
/**
* Send an ack or a nak packet back towards whoever sent idFrom
*/
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0);
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
bool ackWantsAck = false);
private:
/**
@@ -162,4 +175,4 @@ PacketId generatePacketId();
#define BITFIELD_WANT_RESPONSE_SHIFT 1
#define BITFIELD_OK_TO_MQTT_SHIFT 0
#define BITFIELD_WANT_RESPONSE_MASK (1 << BITFIELD_WANT_RESPONSE_SHIFT)
#define BITFIELD_OK_TO_MQTT_MASK (1 << BITFIELD_OK_TO_MQTT_SHIFT)
#define BITFIELD_OK_TO_MQTT_MASK (1 << BITFIELD_OK_TO_MQTT_SHIFT)

View File

@@ -52,7 +52,7 @@ template <typename T> bool SX126xInterface<T>::init()
pinMode(SX126X_POWER_EN, OUTPUT);
#endif
#ifdef HELTEC_V4
#if defined(USE_GC1109_PA)
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
@@ -80,6 +80,9 @@ template <typename T> bool SX126xInterface<T>::init()
RadioLibInterface::init();
limitPower(SX126X_MAX_POWER);
// Make sure we reach the minimum power supported to turn the chip on (-9dBm)
if (power < -9)
power = -9;
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
// \todo Display actual typename of the adapter, not just `SX126x`
@@ -118,8 +121,8 @@ template <typename T> bool SX126xInterface<T>::init()
LOG_DEBUG("Set DIO2 as %sRF switch, result: %d", dio2AsRfSwitch ? "" : "not ", res);
}
// If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has
// no effect
// If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has
// no effect
#if ARCH_PORTDUINO
if (res == RADIOLIB_ERR_NONE) {
LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", portduino_config.lora_rxen_pin.pin,
@@ -349,7 +352,7 @@ template <typename T> bool SX126xInterface<T>::sleep()
digitalWrite(SX126X_POWER_EN, LOW);
#endif
#ifdef HELTEC_V4
#if defined(USE_GC1109_PA)
/*
* Do not switch the power on and off frequently.
* After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
@@ -364,7 +367,7 @@ template <typename T> bool SX126xInterface<T>::sleep()
/** Some boards require GPIO control of tx vs rx paths */
template <typename T> void SX126xInterface<T>::setTransmitEnable(bool txon)
{
#ifdef HELTEC_V4
#if defined(USE_GC1109_PA)
digitalWrite(LORA_PA_POWER, HIGH);
digitalWrite(LORA_PA_EN, HIGH);
digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);

View File

@@ -72,6 +72,8 @@ template <class T> class SX126xInterface : public RadioLibInterface
virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
private:
/** Some boards require GPIO control of tx vs rx paths */
void setTransmitEnable(bool txon);

View File

@@ -67,4 +67,6 @@ template <class T> class SX128xInterface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
};

View File

@@ -132,6 +132,8 @@ typedef struct _meshtastic_SharedContact {
meshtastic_User user;
/* Add this contact to the blocked / ignored list */
bool should_ignore;
/* Set the IS_KEY_MANUALLY_VERIFIED bit */
bool manually_verified;
} meshtastic_SharedContact;
/* This message is used by a client to initiate or complete a key verification */
@@ -319,13 +321,13 @@ extern "C" {
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 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}
#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_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}
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
/* Field tags (for use in manual encoding/decoding) */
@@ -341,6 +343,7 @@ extern "C" {
#define meshtastic_SharedContact_node_num_tag 1
#define meshtastic_SharedContact_user_tag 2
#define meshtastic_SharedContact_should_ignore_tag 3
#define meshtastic_SharedContact_manually_verified_tag 4
#define meshtastic_KeyVerificationAdmin_message_type_tag 1
#define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2
#define meshtastic_KeyVerificationAdmin_nonce_tag 3
@@ -504,7 +507,8 @@ X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 1)
#define meshtastic_SharedContact_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, node_num, 1) \
X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \
X(a, STATIC, SINGULAR, BOOL, should_ignore, 3)
X(a, STATIC, SINGULAR, BOOL, should_ignore, 3) \
X(a, STATIC, SINGULAR, BOOL, manually_verified, 4)
#define meshtastic_SharedContact_CALLBACK NULL
#define meshtastic_SharedContact_DEFAULT NULL
#define meshtastic_SharedContact_user_MSGTYPE meshtastic_User
@@ -539,7 +543,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
#define meshtastic_HamParameters_size 31
#define meshtastic_KeyVerificationAdmin_size 25
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
#define meshtastic_SharedContact_size 125
#define meshtastic_SharedContact_size 127
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
#define meshtastic_ChannelSet_size 679
#define meshtastic_ChannelSet_size 695
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -97,6 +97,8 @@ typedef struct _meshtastic_ChannelSettings {
/* Per-channel module settings. */
bool has_module_settings;
meshtastic_ModuleSettings module_settings;
/* Whether or not we should receive notifactions / alerts through this channel */
bool mute;
} meshtastic_ChannelSettings;
/* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -128,10 +130,10 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
#define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
#define meshtastic_ModuleSettings_init_default {0, 0}
#define meshtastic_Channel_init_default {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
#define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
#define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
#define meshtastic_ModuleSettings_init_zero {0, 0}
#define meshtastic_Channel_init_zero {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
@@ -145,6 +147,7 @@ extern "C" {
#define meshtastic_ChannelSettings_uplink_enabled_tag 5
#define meshtastic_ChannelSettings_downlink_enabled_tag 6
#define meshtastic_ChannelSettings_module_settings_tag 7
#define meshtastic_ChannelSettings_mute_tag 8
#define meshtastic_Channel_index_tag 1
#define meshtastic_Channel_settings_tag 2
#define meshtastic_Channel_role_tag 3
@@ -157,7 +160,8 @@ X(a, STATIC, SINGULAR, STRING, name, 3) \
X(a, STATIC, SINGULAR, FIXED32, id, 4) \
X(a, STATIC, SINGULAR, BOOL, uplink_enabled, 5) \
X(a, STATIC, SINGULAR, BOOL, downlink_enabled, 6) \
X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7)
X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7) \
X(a, STATIC, SINGULAR, BOOL, mute, 8)
#define meshtastic_ChannelSettings_CALLBACK NULL
#define meshtastic_ChannelSettings_DEFAULT NULL
#define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
@@ -187,8 +191,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
#define meshtastic_ChannelSettings_size 72
#define meshtastic_Channel_size 87
#define meshtastic_ChannelSettings_size 74
#define meshtastic_Channel_size 89
#define meshtastic_ModuleSettings_size 8
#ifdef __cplusplus

View File

@@ -26,7 +26,8 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT = 3,
/* Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate. */
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
Deprecated in v2.7.11 because it creates "holes" in the mesh rebroadcast chain. */
meshtastic_Config_DeviceConfig_Role_REPEATER = 4,
/* Description: Broadcasts GPS position packets as priority.
Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default.

View File

@@ -68,6 +68,8 @@ typedef enum _meshtastic_Language {
meshtastic_Language_BULGARIAN = 17,
/* Czech */
meshtastic_Language_CZECH = 18,
/* Danish */
meshtastic_Language_DANISH = 19,
/* Simplified Chinese (experimental) */
meshtastic_Language_SIMPLIFIED_CHINESE = 30,
/* Traditional Chinese (experimental) */

View File

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

View File

@@ -253,8 +253,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 = 99,
/* Seeed Tracker L1 EINK driver */
meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK = 100,
/* Reserved ID for future and past use */
meshtastic_HardwareModel_QWANTZ_TINY_ARMS = 101,
/* Muzi Works R1 Neo */
meshtastic_HardwareModel_MUZI_R1_NEO = 101,
/* Lilygo T-Deck Pro */
meshtastic_HardwareModel_T_DECK_PRO = 102,
/* Lilygo TLora Pager */
@@ -278,6 +278,10 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_M5STACK_C6L = 111,
/* M5Stack Cardputer Adv */
meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
/* ESP32S3 main controller with GPS and TFT screen. */
meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 = 113,
/* LilyGo T-Watch Ultra */
meshtastic_HardwareModel_T_WATCH_ULTRA = 114,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */

View File

@@ -357,6 +357,8 @@ typedef struct _meshtastic_LocalStats {
uint32_t heap_total_bytes;
/* Number of bytes free in the heap */
uint32_t heap_free_bytes;
/* Number of packets that were dropped because the transmit queue was full. */
uint16_t num_tx_dropped;
} meshtastic_LocalStats;
/* Health telemetry metrics */
@@ -454,7 +456,7 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
@@ -463,7 +465,7 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
@@ -551,6 +553,7 @@ extern "C" {
#define meshtastic_LocalStats_num_tx_relay_canceled_tag 11
#define meshtastic_LocalStats_heap_total_bytes_tag 12
#define meshtastic_LocalStats_heap_free_bytes_tag 13
#define meshtastic_LocalStats_num_tx_dropped_tag 14
#define meshtastic_HealthMetrics_heart_bpm_tag 1
#define meshtastic_HealthMetrics_spO2_tag 2
#define meshtastic_HealthMetrics_temperature_tag 3
@@ -672,7 +675,8 @@ X(a, STATIC, SINGULAR, UINT32, num_rx_dupe, 9) \
X(a, STATIC, SINGULAR, UINT32, num_tx_relay, 10) \
X(a, STATIC, SINGULAR, UINT32, num_tx_relay_canceled, 11) \
X(a, STATIC, SINGULAR, UINT32, heap_total_bytes, 12) \
X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13)
X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13) \
X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14)
#define meshtastic_LocalStats_CALLBACK NULL
#define meshtastic_LocalStats_DEFAULT NULL
@@ -749,7 +753,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
#define meshtastic_EnvironmentMetrics_size 113
#define meshtastic_HealthMetrics_size 11
#define meshtastic_HostMetrics_size 264
#define meshtastic_LocalStats_size 72
#define meshtastic_LocalStats_size 76
#define meshtastic_Nau7802Config_size 16
#define meshtastic_PowerMetrics_size 81
#define meshtastic_Telemetry_size 272

View File

@@ -148,6 +148,8 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
{
if (webServerThread)
webServerThread->markActivity();
LOG_DEBUG("webAPI handleAPIv1FromRadio");
@@ -391,6 +393,9 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
void handleStatic(HTTPRequest *req, HTTPResponse *res)
{
if (webServerThread)
webServerThread->markActivity();
// Get access to the parameters
ResourceParameters *params = req->getParams();

View File

@@ -49,6 +49,12 @@ Preferences prefs;
using namespace httpsserver;
#include "mesh/http/ContentHandler.h"
static const uint32_t ACTIVE_THRESHOLD_MS = 5000;
static const uint32_t MEDIUM_THRESHOLD_MS = 30000;
static const int32_t ACTIVE_INTERVAL_MS = 50;
static const int32_t MEDIUM_INTERVAL_MS = 200;
static const int32_t IDLE_INTERVAL_MS = 1000;
static SSLCert *cert;
static HTTPSServer *secureServer;
static HTTPServer *insecureServer;
@@ -175,6 +181,32 @@ WebServerThread::WebServerThread() : concurrency::OSThread("WebServer")
if (!config.network.wifi_enabled && !config.network.eth_enabled) {
disable();
}
lastActivityTime = millis();
}
void WebServerThread::markActivity()
{
lastActivityTime = millis();
}
int32_t WebServerThread::getAdaptiveInterval()
{
uint32_t currentTime = millis();
uint32_t timeSinceActivity;
if (currentTime >= lastActivityTime) {
timeSinceActivity = currentTime - lastActivityTime;
} else {
timeSinceActivity = (UINT32_MAX - lastActivityTime) + currentTime + 1;
}
if (timeSinceActivity < ACTIVE_THRESHOLD_MS) {
return ACTIVE_INTERVAL_MS;
} else if (timeSinceActivity < MEDIUM_THRESHOLD_MS) {
return MEDIUM_INTERVAL_MS;
} else {
return IDLE_INTERVAL_MS;
}
}
int32_t WebServerThread::runOnce()
@@ -189,8 +221,7 @@ int32_t WebServerThread::runOnce()
ESP.restart();
}
// Loop every 5ms.
return (5);
return getAdaptiveInterval();
}
void initWebServer()

View File

@@ -10,13 +10,17 @@ void createSSLCert();
class WebServerThread : private concurrency::OSThread
{
private:
uint32_t lastActivityTime = 0;
public:
WebServerThread();
uint32_t requestRestart = 0;
void markActivity();
protected:
virtual int32_t runOnce() override;
int32_t getAdaptiveInterval();
};
extern WebServerThread *webServerThread;

View File

@@ -554,10 +554,8 @@ void AdminModule::handleSetOwner(const meshtastic_User &o)
changed |= strcmp(owner.short_name, o.short_name);
strncpy(owner.short_name, o.short_name, sizeof(owner.short_name));
}
if (*o.id) {
changed |= strcmp(owner.id, o.id);
strncpy(owner.id, o.id, sizeof(owner.id));
}
snprintf(owner.id, sizeof(owner.id), "!%08x", nodeDB->getNodeNum());
if (owner.is_licensed != o.is_licensed) {
changed = 1;
owner.is_licensed = o.is_licensed;
@@ -611,10 +609,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
}
config.device = c.payload_variant.device;
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_NONE &&
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
meshtastic_Config_DeviceConfig_Role_REPEATER)) {
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
const char *warning = "Rebroadcast mode can't be set to NONE for a router or repeater";
const char *warning = "Rebroadcast mode can't be set to NONE for a router";
LOG_WARN(warning);
sendWarning(warning);
}
@@ -627,8 +624,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d", min_node_info_broadcast_secs);
config.device.node_info_broadcast_secs = min_node_info_broadcast_secs;
}
// Router Client is deprecated; Set it to client
if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) {
// Router Client and Repeater deprecated; Set it to client
if (IS_ONE_OF(c.payload_variant.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT,
meshtastic_Config_DeviceConfig_Role_REPEATER)) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
if (moduleConfig.store_forward.enabled && !moduleConfig.store_forward.is_server) {
moduleConfig.store_forward.is_server = true;
@@ -637,10 +635,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
}
}
#if USERPREFS_EVENT_MODE
// If we're in event mode, nobody is a Router or Repeater
// If we're in event mode, nobody is a Router or Router Late
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
}
#endif
@@ -707,20 +704,40 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
#endif
config.display = c.payload_variant.display;
break;
case meshtastic_Config_lora_tag:
case meshtastic_Config_lora_tag: {
// Wrap the entire case in a block to scope variables and avoid crossing initialization
auto oldLoraConfig = config.lora;
auto validatedLora = c.payload_variant.lora;
LOG_INFO("Set config: LoRa");
config.has_lora = true;
if (validatedLora.coding_rate < 4 || validatedLora.coding_rate > 8) {
LOG_WARN("Invalid coding_rate %d, setting to 5", validatedLora.coding_rate);
validatedLora.coding_rate = 5;
}
if (validatedLora.spread_factor < 7 || validatedLora.spread_factor > 12) {
LOG_WARN("Invalid spread_factor %d, setting to 11", validatedLora.spread_factor);
validatedLora.spread_factor = 11;
}
if (validatedLora.bandwidth == 0) {
int originalBandwidth = validatedLora.bandwidth;
validatedLora.bandwidth = myRegion->wideLora ? 800 : 250;
LOG_WARN("Invalid bandwidth %d, setting to default", originalBandwidth);
}
// If no lora radio parameters change, don't need to reboot
if (config.lora.use_preset == c.payload_variant.lora.use_preset && config.lora.region == c.payload_variant.lora.region &&
config.lora.modem_preset == c.payload_variant.lora.modem_preset &&
config.lora.bandwidth == c.payload_variant.lora.bandwidth &&
config.lora.spread_factor == c.payload_variant.lora.spread_factor &&
config.lora.coding_rate == c.payload_variant.lora.coding_rate &&
config.lora.tx_power == c.payload_variant.lora.tx_power &&
config.lora.frequency_offset == c.payload_variant.lora.frequency_offset &&
config.lora.override_frequency == c.payload_variant.lora.override_frequency &&
config.lora.channel_num == c.payload_variant.lora.channel_num &&
config.lora.sx126x_rx_boosted_gain == c.payload_variant.lora.sx126x_rx_boosted_gain) {
if (oldLoraConfig.use_preset == validatedLora.use_preset && oldLoraConfig.region == validatedLora.region &&
oldLoraConfig.modem_preset == validatedLora.modem_preset && oldLoraConfig.bandwidth == validatedLora.bandwidth &&
oldLoraConfig.spread_factor == validatedLora.spread_factor &&
oldLoraConfig.coding_rate == validatedLora.coding_rate && oldLoraConfig.tx_power == validatedLora.tx_power &&
oldLoraConfig.frequency_offset == validatedLora.frequency_offset &&
oldLoraConfig.override_frequency == validatedLora.override_frequency &&
oldLoraConfig.channel_num == validatedLora.channel_num &&
oldLoraConfig.sx126x_rx_boosted_gain == validatedLora.sx126x_rx_boosted_gain) {
requiresReboot = false;
}
@@ -739,7 +756,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
digitalWrite(RF95_FAN_EN, HIGH ^ 0);
}
#endif
config.lora = c.payload_variant.lora;
config.lora = validatedLora;
// If we're setting region for the first time, init the region and regenerate the keys
if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
if (!owner.is_licensed) {
@@ -765,12 +782,26 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
if (myRegion->dutyCycle < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
}
// Compare the entire string, we are sure of the length as a topic has never been set
if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) {
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
}
}
if (config.lora.region != myRegion->code) {
// Region has changed so check whether there is a regulatory one we should be using instead.
// Additionally as a side-effect, assume a new value under myRegion
initRegion();
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
// Default root is in use, so subscribe to the appropriate MQTT topic for this region
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
}
changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
}
break;
}
case meshtastic_Config_bluetooth_tag:
LOG_INFO("Set config: Bluetooth");
config.has_bluetooth = true;

View File

@@ -69,7 +69,7 @@ bool ascending = true;
#endif
#define EXT_NOTIFICATION_MODULE_OUTPUT_MS 1000
#define EXT_NOTIFICATION_DEFAULT_THREAD_MS 25
#define EXT_NOTIFICATION_FAST_THREAD_MS 25
#define ASCII_BELL 0x07
@@ -88,12 +88,13 @@ int32_t ExternalNotificationModule::runOnce()
if (!moduleConfig.external_notification.enabled) {
return INT32_MAX; // we don't need this thread here...
} else {
bool isPlaying = rtttl::isPlaying();
uint32_t delay = EXT_NOTIFICATION_MODULE_OUTPUT_MS;
bool isRtttlPlaying = rtttl::isPlaying();
#ifdef HAS_I2S
isPlaying = rtttl::isPlaying() || audioThread->isPlaying();
// audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop
isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
#endif
if ((nagCycleCutoff < millis()) && !isPlaying) {
if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
// let the song finish if we reach timeout
nagCycleCutoff = UINT32_MAX;
LOG_INFO("Turning off external notification: ");
@@ -116,21 +117,16 @@ int32_t ExternalNotificationModule::runOnce()
// If the output is turned on, turn it back off after the given period of time.
if (isNagging) {
if (externalTurnedOn[0] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
delay = (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS);
if (externalTurnedOn[0] + delay < millis()) {
setExternalState(0, !getExternal(0));
}
if (externalTurnedOn[1] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
if (externalTurnedOn[1] + delay < millis()) {
setExternalState(1, !getExternal(1));
}
// Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL)
if (!moduleConfig.external_notification.use_pwm &&
externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
if (!moduleConfig.external_notification.use_pwm && externalTurnedOn[2] + delay < millis()) {
LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms,
millis());
setExternalState(2, !getExternal(2));
@@ -181,6 +177,8 @@ int32_t ExternalNotificationModule::runOnce()
colorState = 1;
}
}
// we need fast updates for the color change
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
#endif
#ifdef T_WATCH_S3
@@ -190,12 +188,14 @@ int32_t ExternalNotificationModule::runOnce()
// Play RTTTL over i2s audio interface if enabled as buzzer
#ifdef HAS_I2S
if (moduleConfig.external_notification.use_i2s_as_buzzer && canBuzz()) {
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
if (audioThread->isPlaying()) {
// Continue playing
} else if (isNagging && (nagCycleCutoff >= millis())) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
}
// we need fast updates to play the RTTTL
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
}
#endif
// now let the PWM buzzer play
@@ -206,9 +206,11 @@ int32_t ExternalNotificationModule::runOnce()
// start the song again if we have time left
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
}
// we need fast updates to play the RTTTL
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
}
return EXT_NOTIFICATION_DEFAULT_THREAD_MS;
return delay;
}
}
@@ -440,7 +442,7 @@ ExternalNotificationModule::ExternalNotificationModule()
ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp)
{
if (moduleConfig.external_notification.enabled && !isMuted) {
if (moduleConfig.external_notification.enabled && !isSilenced) {
#ifdef T_WATCH_S3
drv.setWaveform(0, 75);
drv.setWaveform(1, 56);
@@ -451,12 +453,13 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
// Check if the message contains a bell character. Don't do this loop for every pin, just once.
auto &p = mp.decoded;
bool containsBell = false;
for (int i = 0; i < p.payload.size; i++) {
for (size_t i = 0; i < p.payload.size; i++) {
if (p.payload.bytes[i] == ASCII_BELL) {
containsBell = true;
}
}
meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex());
if (moduleConfig.external_notification.alert_bell) {
if (containsBell) {
LOG_INFO("externalNotificationModule - Notification Bell");
@@ -507,7 +510,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
if (moduleConfig.external_notification.alert_message) {
if (moduleConfig.external_notification.alert_message && !ch.settings.mute) {
LOG_INFO("externalNotificationModule - Notification Module");
isNagging = true;
setExternalState(0, true);
@@ -518,7 +521,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
if (moduleConfig.external_notification.alert_message_vibra) {
if (moduleConfig.external_notification.alert_message_vibra && !ch.settings.mute) {
LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
isNagging = true;
setExternalState(1, true);
@@ -529,25 +532,32 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
if (moduleConfig.external_notification.alert_message_buzzer) {
if (moduleConfig.external_notification.alert_message_buzzer && !ch.settings.mute) {
LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
isNagging = true;
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
setExternalState(2, true);
} else {
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(!isBroadcast(mp.to) && isToUs(&mp))) {
// Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
isNagging = true;
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
setExternalState(2, true);
} else {
#ifdef HAS_I2S
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
} else
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
} else
#endif
if (moduleConfig.external_notification.use_pwm) {
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
if (moduleConfig.external_notification.use_pwm) {
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
}
}
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
// Don't beep if buzzer mode is "direct messages only" and it is no direct message
LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY");
}
}
setIntervalFromNow(0); // run once so we know if we should do something

View File

@@ -43,8 +43,8 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
void setExternalState(uint8_t index = 0, bool on = false);
bool getExternal(uint8_t index = 0);
void setMute(bool mute) { isMuted = mute; }
bool getMute() { return isMuted; }
void setMute(bool mute) { isSilenced = mute; }
bool getMute() { return isSilenced; }
bool canBuzz();
bool nagging();
@@ -67,7 +67,7 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
bool isNagging = false;
bool isMuted = false;
bool isSilenced = false;
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,

View File

@@ -112,206 +112,192 @@
*/
void setupModules()
{
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
inputBroker = new InputBroker();
systemCommandsModule = new SystemCommandsModule();
buzzerFeedbackThread = new BuzzerFeedbackThread();
}
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
inputBroker = new InputBroker();
systemCommandsModule = new SystemCommandsModule();
buzzerFeedbackThread = new BuzzerFeedbackThread();
}
#endif
#if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule();
adminModule = new AdminModule();
#endif
#if !MESHTASTIC_EXCLUDE_NODEINFO
nodeInfoModule = new NodeInfoModule();
nodeInfoModule = new NodeInfoModule();
#endif
#if !MESHTASTIC_EXCLUDE_GPS
positionModule = new PositionModule();
positionModule = new PositionModule();
#endif
#if !MESHTASTIC_EXCLUDE_WAYPOINT
waypointModule = new WaypointModule();
waypointModule = new WaypointModule();
#endif
#if !MESHTASTIC_EXCLUDE_TEXTMESSAGE
textMessageModule = new TextMessageModule();
textMessageModule = new TextMessageModule();
#endif
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
traceRouteModule = new TraceRouteModule();
traceRouteModule = new TraceRouteModule();
#endif
#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) {
neighborInfoModule = new NeighborInfoModule();
}
if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) {
neighborInfoModule = new NeighborInfoModule();
}
#endif
#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) {
detectionSensorModule = new DetectionSensorModule();
}
if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) {
detectionSensorModule = new DetectionSensorModule();
}
#endif
#if !MESHTASTIC_EXCLUDE_ATAK
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK,
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) {
atakPluginModule = new AtakPluginModule();
}
if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK ||
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) {
atakPluginModule = new AtakPluginModule();
}
#endif
#if !MESHTASTIC_EXCLUDE_PKI
keyVerificationModule = new KeyVerificationModule();
keyVerificationModule = new KeyVerificationModule();
#endif
#if !MESHTASTIC_EXCLUDE_DROPZONE
dropzoneModule = new DropzoneModule();
dropzoneModule = new DropzoneModule();
#endif
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
new GenericThreadModule();
new GenericThreadModule();
#endif
// Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
// to a global variable.
// Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
// to a global variable.
#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
new RemoteHardwareModule();
new RemoteHardwareModule();
#endif
#if !MESHTASTIC_EXCLUDE_POWERSTRESS
new PowerStressModule();
new PowerStressModule();
#endif
// Example: Put your module here
// new ReplyModule();
// Example: Put your module here
// new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr;
}
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr;
}
#ifdef T_LORA_PAGER
// use a special FSM based rotary encoder version for T-LoRa Pager
rotaryEncoderImpl = new RotaryEncoderImpl();
if (!rotaryEncoderImpl->init()) {
delete rotaryEncoderImpl;
rotaryEncoderImpl = nullptr;
}
// use a special FSM based rotary encoder version for T-LoRa Pager
rotaryEncoderImpl = new RotaryEncoderImpl();
if (!rotaryEncoderImpl->init()) {
delete rotaryEncoderImpl;
rotaryEncoderImpl = nullptr;
}
#else
upDownInterruptImpl1 = new UpDownInterruptImpl1();
if (!upDownInterruptImpl1->init()) {
delete upDownInterruptImpl1;
upDownInterruptImpl1 = nullptr;
}
upDownInterruptImpl1 = new UpDownInterruptImpl1();
if (!upDownInterruptImpl1->init()) {
delete upDownInterruptImpl1;
upDownInterruptImpl1 = nullptr;
}
#endif
cardKbI2cImpl = new CardKbI2cImpl();
cardKbI2cImpl->init();
cardKbI2cImpl = new CardKbI2cImpl();
cardKbI2cImpl->init();
#if defined(M5STACK_UNITC6L)
i2cButton = new i2cButtonThread("i2cButtonThread");
i2cButton = new i2cButtonThread("i2cButtonThread");
#endif
#ifdef INPUTBROKER_MATRIX_TYPE
kbMatrixImpl = new KbMatrixImpl();
kbMatrixImpl->init();
kbMatrixImpl = new KbMatrixImpl();
kbMatrixImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
#ifdef INPUTBROKER_SERIAL_TYPE
aSerialKeyboardImpl = new SerialKeyboardImpl();
aSerialKeyboardImpl->init();
aSerialKeyboardImpl = new SerialKeyboardImpl();
aSerialKeyboardImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
}
}
#endif // HAS_BUTTON
#if ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
seesawRotary = new SeesawRotary("SeesawRotary");
if (!seesawRotary->init()) {
delete seesawRotary;
seesawRotary = nullptr;
}
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
seesawRotary = new SeesawRotary("SeesawRotary");
if (!seesawRotary->init()) {
delete seesawRotary;
seesawRotary = nullptr;
}
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
}
#endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
trackballInterruptImpl1 = new TrackballInterruptImpl1();
trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
}
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
trackballInterruptImpl1 = new TrackballInterruptImpl1();
trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
}
#endif
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
expressLRSFiveWayInput = new ExpressLRSFiveWay();
expressLRSFiveWayInput = new ExpressLRSFiveWay();
#endif
#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
cannedMessageModule = new CannedMessageModule();
}
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
cannedMessageModule = new CannedMessageModule();
}
#endif
#if ARCH_PORTDUINO
new HostMetricsModule();
new HostMetricsModule();
#endif
#if HAS_TELEMETRY
new DeviceTelemetryModule();
new DeviceTelemetryModule();
#endif
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
new EnvironmentTelemetryModule();
}
if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
new EnvironmentTelemetryModule();
}
#if __has_include("Adafruit_PM25AQI.h")
if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
new AirQualityTelemetryModule();
}
if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
new AirQualityTelemetryModule();
}
#endif
#if !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 ||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) {
new HealthTelemetryModule();
}
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 ||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) {
new HealthTelemetryModule();
}
#endif
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.power_measurement_enabled || moduleConfig.telemetry.power_screen_enabled)) {
new PowerTelemetryModule();
}
if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.power_measurement_enabled || moduleConfig.telemetry.power_screen_enabled)) {
new PowerTelemetryModule();
}
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \
!defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#if !MESHTASTIC_EXCLUDE_SERIAL
if (moduleConfig.has_serial && moduleConfig.serial.enabled &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
new SerialModule();
}
if (moduleConfig.has_serial && moduleConfig.serial.enabled &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
new SerialModule();
}
#endif
#endif
#ifdef ARCH_ESP32
// Only run on an esp32 based device.
// Only run on an esp32 based device.
#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO
audioModule = new AudioModule();
audioModule = new AudioModule();
#endif
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
if (moduleConfig.has_paxcounter && moduleConfig.paxcounter.enabled) {
paxcounterModule = new PaxcounterModule();
}
if (moduleConfig.has_paxcounter && moduleConfig.paxcounter.enabled) {
paxcounterModule = new PaxcounterModule();
}
#endif
#endif
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) {
storeForwardModule = new StoreForwardModule();
}
if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) {
storeForwardModule = new StoreForwardModule();
}
#endif
#endif
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
if (moduleConfig.has_external_notification && moduleConfig.external_notification.enabled) {
externalNotificationModule = new ExternalNotificationModule();
}
externalNotificationModule = new ExternalNotificationModule();
#endif
#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
new RangeTestModule();
if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
new RangeTestModule();
#endif
} else {
#if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule();
#endif
#if HAS_TELEMETRY
new DeviceTelemetryModule();
#endif
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
traceRouteModule = new TraceRouteModule();
#endif
}
// NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra
// acks
routingModule = new RoutingModule();

View File

@@ -30,14 +30,32 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
bool wasBroadcast = isBroadcast(mp.to);
// LOG_DEBUG("did encode");
// if user has changed while packet was not for us, inform phone
if (hasChanged && !wasBroadcast && !isToUs(&mp))
service->sendToPhone(packetPool.allocCopy(mp));
if (hasChanged && !wasBroadcast && !isToUs(&mp)) {
auto packetCopy = packetPool.allocCopy(mp); // Keep a copy of the packet for later analysis
// Re-encode the user protobuf, as we have stripped out the user.id
packetCopy->decoded.payload.size = pb_encode_to_bytes(
packetCopy->decoded.payload.bytes, sizeof(packetCopy->decoded.payload.bytes), &meshtastic_User_msg, &p);
service->sendToPhone(packetCopy);
}
// LOG_DEBUG("did handleReceived");
return false; // Let others look at this message also if they want
}
void NodeInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p)
{
// Coerce user.id to be derived from the node number
snprintf(p->id, sizeof(p->id), "!%08x", getFrom(&mp));
// Re-encode the altered protobuf back into the packet
mp.decoded.payload.size =
pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_User_msg, p);
}
void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t channel, bool _shorterTimeout)
{
// cancel any not yet sent (now stale) position packets
@@ -94,12 +112,9 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
u.public_key.bytes[0] = 0;
u.public_key.size = 0;
}
// Coerce unmessagable for Repeater role
if (u.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
u.has_is_unmessagable = true;
u.is_unmessagable = true;
}
// Clear the user.id field since it should be derived from node number on the receiving end
u.id[0] = '\0';
LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name);
lastSentToMesh = millis();
return allocDataProtobuf(u);

View File

@@ -30,6 +30,9 @@ class NodeInfoModule : public ProtobufModule<meshtastic_User>, private concurren
*/
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *p) override;
/** Called to alter received User protobuf */
virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p) override;
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual meshtastic_MeshPacket *allocReply() override;

View File

@@ -41,12 +41,12 @@ int32_t RangeTestModule::runOnce()
// moduleConfig.range_test.enabled = 1;
// moduleConfig.range_test.sender = 30;
// moduleConfig.range_test.save = 1;
// moduleConfig.range_test.clear_on_reboot = 1;
// Fixed position is useful when testing indoors.
// config.position.fixed_position = 1;
uint32_t senderHeartbeat = moduleConfig.range_test.sender * 1000;
if (moduleConfig.range_test.enabled) {
if (firstTime) {
@@ -54,6 +54,11 @@ int32_t RangeTestModule::runOnce()
firstTime = 0;
if (moduleConfig.range_test.clear_on_reboot) {
// User wants to delete previous range test(s)
LOG_INFO("Range Test Module - Clearing out previous test file");
rangeTestModuleRadio->removeFile();
}
if (moduleConfig.range_test.sender) {
LOG_INFO("Init Range Test Module -- Sender");
started = millis(); // make a note of when we started
@@ -141,7 +146,6 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
*/
if (!isFromUs(&mp)) {
if (moduleConfig.range_test.save) {
appendFile(mp);
}
@@ -295,7 +299,42 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
fileToAppend.printf("\"%s\"\n", p.payload.bytes);
fileToAppend.flush();
fileToAppend.close();
#endif
return 1;
#else
LOG_ERROR("Failed to store range test results - feature only available for ESP32");
return 0;
#endif
}
bool RangeTestModuleRadio::removeFile()
{
#ifdef ARCH_ESP32
if (!FSBegin()) {
LOG_DEBUG("An Error has occurred while mounting the filesystem");
return 0;
}
if (!FSCom.exists("/static/rangetest.csv")) {
LOG_DEBUG("No range tests found.");
return 0;
}
LOG_INFO("Deleting previous range test.");
bool result = FSCom.remove("/static/rangetest.csv");
if (!result) {
LOG_ERROR("Failed to delete range test.");
return 0;
}
LOG_INFO("Range test removed.");
return 1;
#else
LOG_ERROR("Failed to remove range test results - feature only available for ESP32");
return 0;
#endif
}

View File

@@ -44,6 +44,11 @@ class RangeTestModuleRadio : public SinglePortModule
*/
bool appendFile(const meshtastic_MeshPacket &mp);
/**
* Cleanup range test data from filesystem
*/
bool removeFile();
protected:
/** Called to handle a particular incoming message

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