Merge branch 'develop' into fix/tlora-pager-rtc

This commit is contained in:
Manuel 2025-10-01 17:50:06 +02:00 committed by GitHub
commit 8b0d88fae3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 210 additions and 107 deletions

View File

@ -9,7 +9,7 @@ plugins:
lint: lint:
enabled: enabled:
- checkov@3.2.471 - checkov@3.2.471
- renovate@41.127.2 - renovate@41.130.1
- prettier@3.6.2 - prettier@3.6.2
- trufflehog@3.90.8 - trufflehog@3.90.8
- yamllint@1.37.1 - yamllint@1.37.1

View File

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

View File

@ -56,6 +56,7 @@ build_flags = -Wno-missing-field-initializers
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
#-D OLED_PL=1 #-D OLED_PL=1
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs #-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_speed = 115200
monitor_filters = direct monitor_filters = direct

@ -1 +1 @@
Subproject commit 46b81e822af1b8e408f437092337f129dee693e6 Subproject commit 082bb7cfeb2cba9d41be139cd324c4b43a14b3f9

View File

@ -26,6 +26,7 @@ class AudioThread : public concurrency::OSThread
i2sRtttl->begin(rtttlFile, audioOut); i2sRtttl->begin(rtttlFile, audioOut);
} }
// Also handles actually playing the RTTTL, needs to be called in loop
bool isPlaying() bool isPlaying()
{ {
if (i2sRtttl != nullptr) { if (i2sRtttl != nullptr) {

View File

@ -6,6 +6,14 @@
#include "configuration.h" #include "configuration.h"
#include "time.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 #ifdef RP2040_SLOW_CLOCK
#define Port Serial2 #define Port Serial2
#else #else
@ -22,7 +30,12 @@ SerialConsole *console;
void consoleInit() 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 DEBUG_PORT.rpInit(); // Simply sets up semaphore
} }
@ -65,14 +78,21 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
int32_t SerialConsole::runOnce() int32_t SerialConsole::runOnce()
{ {
#ifdef HELTEC_MESH_SOLAR #ifdef HELTEC_MESH_SOLAR
//After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module. // 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 if (moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port &&
&& moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) {
{
return 250; return 250;
} }
#endif #endif
return runOncePart();
int32_t delay = runOncePart();
#if defined(SERIAL_HAS_ON_RECEIVE)
return Port.available() ? delay : INT32_MAX;
#elif defined(IS_USB_SERIAL)
return HWCDC::isPlugged() ? delay : (1000 * 20);
#else
return delay;
#endif
} }
void SerialConsole::flush() void SerialConsole::flush()
@ -80,6 +100,18 @@ void SerialConsole::flush()
Port.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 // 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() bool SerialConsole::checkIsConnected()
{ {

View File

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

View File

@ -5,7 +5,7 @@
BuzzerFeedbackThread *buzzerFeedbackThread; BuzzerFeedbackThread *buzzerFeedbackThread;
BuzzerFeedbackThread::BuzzerFeedbackThread() : OSThread("BuzzerFeedback") BuzzerFeedbackThread::BuzzerFeedbackThread()
{ {
if (inputBroker) if (inputBroker)
inputObserver.observe(inputBroker); inputObserver.observe(inputBroker);
@ -19,10 +19,6 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
return 0; // Let other handlers process the event 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 // Handle different input events with appropriate buzzer feedback
switch (event->inputEvent) { switch (event->inputEvent) {
case INPUT_BROKER_USER_PRESS: case INPUT_BROKER_USER_PRESS:
@ -61,15 +57,4 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
} }
return 0; // Allow other handlers to process the 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 "concurrency/OSThread.h"
#include "input/InputBroker.h" #include "input/InputBroker.h"
class BuzzerFeedbackThread : public concurrency::OSThread class BuzzerFeedbackThread
{ {
CallbackObserver<BuzzerFeedbackThread, const InputEvent *> inputObserver = CallbackObserver<BuzzerFeedbackThread, const InputEvent *> inputObserver =
CallbackObserver<BuzzerFeedbackThread, const InputEvent *>(this, &BuzzerFeedbackThread::handleInputEvent); CallbackObserver<BuzzerFeedbackThread, const InputEvent *>(this, &BuzzerFeedbackThread::handleInputEvent);
@ -12,13 +12,6 @@ class BuzzerFeedbackThread : public concurrency::OSThread
public: public:
BuzzerFeedbackThread(); BuzzerFeedbackThread();
int handleInputEvent(const InputEvent *event); int handleInputEvent(const InputEvent *event);
protected:
virtual int32_t runOnce() override;
private:
uint32_t lastEventTime = 0;
bool needsUpdate = false;
}; };
extern BuzzerFeedbackThread *buzzerFeedbackThread; extern BuzzerFeedbackThread *buzzerFeedbackThread;

View File

@ -90,7 +90,9 @@ void OSThread::run()
if (heap < newHeap) if (heap < newHeap)
LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap); LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
#endif #endif
#ifdef DEBUG_LOOP_TIMING
LOG_DEBUG("====== Thread next run in: %d", newDelay);
#endif
runned(); runned();
if (newDelay >= 0) 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 #define SX126X_MAX_POWER 22
#endif #endif
#ifdef HELTEC_V4
// 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 // Default system gain to 0 if not defined
#ifndef TX_GAIN_LORA #ifndef TX_GAIN_LORA
#define TX_GAIN_LORA 0 #define TX_GAIN_LORA 0

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 iconX = iconRightEdge - mute_symbol_big_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2; int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
if (isInverted) { if (isInverted && !force_no_invert) {
display->setColor(WHITE); display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2); display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2);
display->setColor(BLACK); display->setColor(BLACK);

View File

@ -852,24 +852,31 @@ void menuHandler::GPSFormatMenu()
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == 1) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC; uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 2) { } else if (selected == 2) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS; uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 3) { } else if (selected == 3) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM; uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 4) { } else if (selected == 4) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS; uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 5) { } else if (selected == 5) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC; uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 6) { } else if (selected == 6) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR; uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 7) { } else if (selected == 7) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS; uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
} else { } else {
menuQueue = position_base_menu; menuQueue = position_base_menu;

View File

@ -125,8 +125,10 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
char displayLine[32]; char displayLine[32];
if (!gps->getIsConnected() && !config.position.fixed_position) { if (!gps->getIsConnected() && !config.position.fixed_position) {
strcpy(displayLine, "No GPS present"); if (strcmp(mode, "line1") == 0) {
display->drawString(x, y, displayLine); strcpy(displayLine, "No GPS present");
display->drawString(x, y, displayLine);
}
} else if (!gps->getHasLock() && !config.position.fixed_position) { } else if (!gps->getHasLock() && !config.position.fixed_position) {
if (strcmp(mode, "line1") == 0) { if (strcmp(mode, "line1") == 0) {
strcpy(displayLine, "No GPS Lock"); strcpy(displayLine, "No GPS Lock");
@ -1103,6 +1105,18 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
// === Fourth Row: Line 2 GPS Info === // === Fourth Row: Line 2 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2"); 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) #if !defined(M5STACK_UNITC6L)
// === Draw Compass if heading is valid === // === Draw Compass if heading is valid ===

View File

@ -274,7 +274,12 @@ int32_t ButtonThread::runOnce()
} }
} }
btnEvent = BUTTON_EVENT_NONE; 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 INT32_MAX;
} }
/* /*

View File

@ -1002,6 +1002,7 @@ void setup()
config.pullupSense = INPUT_PULLUP; config.pullupSense = INPUT_PULLUP;
config.intRoutine = []() { config.intRoutine = []() {
UserButtonThread->userButton.tick(); UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1022,6 +1023,7 @@ void setup()
touchConfig.pullupSense = pullup_sense; touchConfig.pullupSense = pullup_sense;
touchConfig.intRoutine = []() { touchConfig.intRoutine = []() {
TouchButtonThread->userButton.tick(); TouchButtonThread->userButton.tick();
TouchButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1041,6 +1043,7 @@ void setup()
cancelConfig.pullupSense = pullup_sense; cancelConfig.pullupSense = pullup_sense;
cancelConfig.intRoutine = []() { cancelConfig.intRoutine = []() {
CancelButtonThread->userButton.tick(); CancelButtonThread->userButton.tick();
CancelButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1061,6 +1064,7 @@ void setup()
backConfig.pullupSense = pullup_sense; backConfig.pullupSense = pullup_sense;
backConfig.intRoutine = []() { backConfig.intRoutine = []() {
BackButtonThread->userButton.tick(); BackButtonThread->userButton.tick();
BackButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1095,6 +1099,7 @@ void setup()
userConfig.pullupSense = pullup_sense; userConfig.pullupSense = pullup_sense;
userConfig.intRoutine = []() { userConfig.intRoutine = []() {
UserButtonThread->userButton.tick(); UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1112,6 +1117,7 @@ void setup()
userConfigNoScreen.pullupSense = pullup_sense; userConfigNoScreen.pullupSense = pullup_sense;
userConfigNoScreen.intRoutine = []() { userConfigNoScreen.intRoutine = []() {
UserButtonThread->userButton.tick(); UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1607,6 +1613,9 @@ void loop()
// We want to sleep as long as possible here - because it saves power // We want to sleep as long as possible here - because it saves power
if (!runASAP && loopCanSleep()) { if (!runASAP && loopCanSleep()) {
#ifdef DEBUG_LOOP_TIMING
LOG_DEBUG("main loop delay: %d", delayMsec);
#endif
mainDelay.delay(delayMsec); mainDelay.delay(delayMsec);
} }
} }

View File

@ -54,6 +54,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
// We already enqueued the improved copy, so make sure the incoming packet stops here. // We already enqueued the improved copy, so make sure the incoming packet stops here.
return true; return true;
} }
// No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
// delivering the same packet to applications/phone twice with different hop limits.
seenRecently = true;
} }
if (seenRecently) { if (seenRecently) {

View File

@ -64,6 +64,10 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
// We already enqueued the improved copy, so make sure the incoming packet stops here. // We already enqueued the improved copy, so make sure the incoming packet stops here.
return true; return true;
} }
// No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
// delivering the same packet to applications/phone twice with different hop limits.
seenRecently = true;
} }
if (seenRecently) { if (seenRecently) {

View File

@ -701,7 +701,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#ifdef USERPREFS_NETWORK_ENABLED_PROTOCOLS #ifdef USERPREFS_NETWORK_ENABLED_PROTOCOLS
config.network.enabled_protocols = USERPREFS_NETWORK_ENABLED_PROTOCOLS; config.network.enabled_protocols = USERPREFS_NETWORK_ENABLED_PROTOCOLS;
#else #else
config.network.enabled_protocols = 1; config.network.enabled_protocols = 0;
#endif #endif
#endif #endif
@ -1667,9 +1667,6 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
return false; return false;
} }
LOG_INFO("Public Key set for node, not updating!"); 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) { } else if (p.public_key.size == 32) {
LOG_INFO("Update Node Pubkey!"); LOG_INFO("Update Node Pubkey!");
} }

View File

@ -674,11 +674,25 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
power = maxPower; power = maxPower;
} }
#ifndef NUM_PA_POINTS
if (TX_GAIN_LORA > 0) { if (TX_GAIN_LORA > 0) {
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA); LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA);
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 if (power > loraMaxPower) // Clamp power to maximum defined level
power = loraMaxPower; power = loraMaxPower;

View File

@ -90,7 +90,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
// For subsequent hops, check if previous relay is a favorite router // For subsequent hops, check if previous relay is a favorite router
// Optimized search for favorite routers with matching last byte // Optimized search for favorite routers with matching last byte
// Check ordering optimized for IoT devices (cheapest checks first) // Check ordering optimized for IoT devices (cheapest checks first)
for (int i = 0; i < nodeDB->getNumMeshNodes(); i++) { for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
if (!node) if (!node)
continue; continue;
@ -105,7 +105,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
// Check 3: role check (moderate cost - multiple comparisons) // Check 3: role check (moderate cost - multiple comparisons)
if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER, if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) { meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
continue; continue;
} }
@ -483,35 +483,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) { if (decrypted) {
// parsing was successful // parsing was successful
p->channel = chIndex; // change to store the index instead of the hash p->channel = chIndex; // change to store the index instead of the hash

View File

@ -80,6 +80,9 @@ template <typename T> bool SX126xInterface<T>::init()
RadioLibInterface::init(); RadioLibInterface::init();
limitPower(SX126X_MAX_POWER); 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); int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
// \todo Display actual typename of the adapter, not just `SX126x` // \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); 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 // 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 // no effect
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (res == RADIOLIB_ERR_NONE) { 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, LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", portduino_config.lora_rxen_pin.pin,

View File

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

View File

@ -97,6 +97,8 @@ typedef struct _meshtastic_ChannelSettings {
/* Per-channel module settings. */ /* Per-channel module settings. */
bool has_module_settings; bool has_module_settings;
meshtastic_ModuleSettings module_settings; meshtastic_ModuleSettings module_settings;
/* Whether or not we should receive notifactions / alerts through this channel */
bool mute;
} meshtastic_ChannelSettings; } meshtastic_ChannelSettings;
/* A pair of a channel number, mode and the (sharable) settings for that channel */ /* 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 */ /* 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_ModuleSettings_init_default {0, 0}
#define meshtastic_Channel_init_default {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN} #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_ModuleSettings_init_zero {0, 0}
#define meshtastic_Channel_init_zero {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN} #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_uplink_enabled_tag 5
#define meshtastic_ChannelSettings_downlink_enabled_tag 6 #define meshtastic_ChannelSettings_downlink_enabled_tag 6
#define meshtastic_ChannelSettings_module_settings_tag 7 #define meshtastic_ChannelSettings_module_settings_tag 7
#define meshtastic_ChannelSettings_mute_tag 8
#define meshtastic_Channel_index_tag 1 #define meshtastic_Channel_index_tag 1
#define meshtastic_Channel_settings_tag 2 #define meshtastic_Channel_settings_tag 2
#define meshtastic_Channel_role_tag 3 #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, FIXED32, id, 4) \
X(a, STATIC, SINGULAR, BOOL, uplink_enabled, 5) \ X(a, STATIC, SINGULAR, BOOL, uplink_enabled, 5) \
X(a, STATIC, SINGULAR, BOOL, downlink_enabled, 6) \ 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_CALLBACK NULL
#define meshtastic_ChannelSettings_DEFAULT NULL #define meshtastic_ChannelSettings_DEFAULT NULL
#define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings #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) */ /* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
#define meshtastic_ChannelSettings_size 72 #define meshtastic_ChannelSettings_size 74
#define meshtastic_Channel_size 87 #define meshtastic_Channel_size 89
#define meshtastic_ModuleSettings_size 8 #define meshtastic_ModuleSettings_size 8
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -26,7 +26,8 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT = 3, 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. /* 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 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, meshtastic_Config_DeviceConfig_Role_REPEATER = 4,
/* Description: Broadcasts GPS position packets as priority. /* Description: Broadcasts GPS position packets as priority.
Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default. Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default.

View File

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

View File

@ -69,7 +69,7 @@ bool ascending = true;
#endif #endif
#define EXT_NOTIFICATION_MODULE_OUTPUT_MS 1000 #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 #define ASCII_BELL 0x07
@ -88,12 +88,13 @@ int32_t ExternalNotificationModule::runOnce()
if (!moduleConfig.external_notification.enabled) { if (!moduleConfig.external_notification.enabled) {
return INT32_MAX; // we don't need this thread here... return INT32_MAX; // we don't need this thread here...
} else { } else {
uint32_t delay = EXT_NOTIFICATION_MODULE_OUTPUT_MS;
bool isPlaying = rtttl::isPlaying(); bool isRtttlPlaying = rtttl::isPlaying();
#ifdef HAS_I2S #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 #endif
if ((nagCycleCutoff < millis()) && !isPlaying) { if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
// let the song finish if we reach timeout // let the song finish if we reach timeout
nagCycleCutoff = UINT32_MAX; nagCycleCutoff = UINT32_MAX;
LOG_INFO("Turning off external notification: "); 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 the output is turned on, turn it back off after the given period of time.
if (isNagging) { if (isNagging) {
if (externalTurnedOn[0] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms delay = (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) < : EXT_NOTIFICATION_MODULE_OUTPUT_MS);
millis()) { if (externalTurnedOn[0] + delay < millis()) {
setExternalState(0, !getExternal(0)); setExternalState(0, !getExternal(0));
} }
if (externalTurnedOn[1] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms if (externalTurnedOn[1] + delay < millis()) {
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
setExternalState(1, !getExternal(1)); setExternalState(1, !getExternal(1));
} }
// Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL) // Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL)
if (!moduleConfig.external_notification.use_pwm && if (!moduleConfig.external_notification.use_pwm && externalTurnedOn[2] + delay < millis()) {
externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms, LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms,
millis()); millis());
setExternalState(2, !getExternal(2)); setExternalState(2, !getExternal(2));
@ -181,6 +177,8 @@ int32_t ExternalNotificationModule::runOnce()
colorState = 1; colorState = 1;
} }
} }
// we need fast updates for the color change
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
#endif #endif
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
@ -190,12 +188,14 @@ int32_t ExternalNotificationModule::runOnce()
// Play RTTTL over i2s audio interface if enabled as buzzer // Play RTTTL over i2s audio interface if enabled as buzzer
#ifdef HAS_I2S #ifdef HAS_I2S
if (moduleConfig.external_notification.use_i2s_as_buzzer && canBuzz()) { if (moduleConfig.external_notification.use_i2s_as_buzzer) {
if (audioThread->isPlaying()) { if (audioThread->isPlaying()) {
// Continue playing // Continue playing
} else if (isNagging && (nagCycleCutoff >= millis())) { } else if (isNagging && (nagCycleCutoff >= millis())) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
} }
// we need fast updates to play the RTTTL
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
} }
#endif #endif
// now let the PWM buzzer play // now let the PWM buzzer play
@ -206,9 +206,11 @@ int32_t ExternalNotificationModule::runOnce()
// start the song again if we have time left // start the song again if we have time left
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); 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;
} }
} }

View File

@ -41,12 +41,12 @@ int32_t RangeTestModule::runOnce()
// moduleConfig.range_test.enabled = 1; // moduleConfig.range_test.enabled = 1;
// moduleConfig.range_test.sender = 30; // moduleConfig.range_test.sender = 30;
// moduleConfig.range_test.save = 1; // moduleConfig.range_test.save = 1;
// moduleConfig.range_test.clear_on_reboot = 1;
// Fixed position is useful when testing indoors. // Fixed position is useful when testing indoors.
// config.position.fixed_position = 1; // config.position.fixed_position = 1;
uint32_t senderHeartbeat = moduleConfig.range_test.sender * 1000; uint32_t senderHeartbeat = moduleConfig.range_test.sender * 1000;
if (moduleConfig.range_test.enabled) { if (moduleConfig.range_test.enabled) {
if (firstTime) { if (firstTime) {
@ -54,6 +54,11 @@ int32_t RangeTestModule::runOnce()
firstTime = 0; 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) { if (moduleConfig.range_test.sender) {
LOG_INFO("Init Range Test Module -- Sender"); LOG_INFO("Init Range Test Module -- Sender");
started = millis(); // make a note of when we started started = millis(); // make a note of when we started
@ -141,7 +146,6 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
*/ */
if (!isFromUs(&mp)) { if (!isFromUs(&mp)) {
if (moduleConfig.range_test.save) { if (moduleConfig.range_test.save) {
appendFile(mp); appendFile(mp);
} }
@ -295,7 +299,42 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
fileToAppend.printf("\"%s\"\n", p.payload.bytes); fileToAppend.printf("\"%s\"\n", p.payload.bytes);
fileToAppend.flush(); fileToAppend.flush();
fileToAppend.close(); fileToAppend.close();
#endif
return 1; 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); bool appendFile(const meshtastic_MeshPacket &mp);
/**
* Cleanup range test data from filesystem
*/
bool removeFile();
protected: protected:
/** Called to handle a particular incoming message /** Called to handle a particular incoming message

View File

@ -53,7 +53,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
hasChecked = true; hasChecked = true;
} }
return 100; // the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
return INT32_MAX;
} }
/** /**
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)

View File

@ -224,7 +224,7 @@ extern struct portduino_config_struct {
out << YAML::Key << "RF95_MAX_POWER" << YAML::Value << rf95_max_power; out << YAML::Key << "RF95_MAX_POWER" << YAML::Value << rf95_max_power;
out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch; out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch;
if (dio3_tcxo_voltage != 0) if (dio3_tcxo_voltage != 0)
out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << dio3_tcxo_voltage; out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << YAML::Precision(3) << (float)dio3_tcxo_voltage / 1000;
if (lora_usb_pid != 0x5512) if (lora_usb_pid != 0x5512)
out << YAML::Key << "USB_PID" << YAML::Value << YAML::Hex << lora_usb_pid; out << YAML::Key << "USB_PID" << YAML::Value << YAML::Hex << lora_usb_pid;
if (lora_usb_vid != 0x1A86) if (lora_usb_vid != 0x1A86)

View File

@ -8,4 +8,3 @@ build_flags =
-D HELTEC_V4 -D HELTEC_V4
-I variants/esp32s3/heltec_v4 -I variants/esp32s3/heltec_v4
-D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-D SX126X_MAX_POWER=11