diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index e9bbba9ff..4e9de6a02 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,7 +9,7 @@ plugins: lint: enabled: - checkov@3.2.471 - - renovate@41.127.2 + - renovate@41.130.1 - prettier@3.6.2 - trufflehog@3.90.8 - yamllint@1.37.1 diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 8990053eb..d2c933461 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -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 diff --git a/platformio.ini b/platformio.ini index bd2e8fa37..f6c0f3867 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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 diff --git a/protobufs b/protobufs index 46b81e822..082bb7cfe 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 46b81e822af1b8e408f437092337f129dee693e6 +Subproject commit 082bb7cfeb2cba9d41be139cd324c4b43a14b3f9 diff --git a/src/AudioThread.h b/src/AudioThread.h index 286729909..cb1f804ac 100644 --- a/src/AudioThread.h +++ b/src/AudioThread.h @@ -26,6 +26,7 @@ class AudioThread : public concurrency::OSThread i2sRtttl->begin(rtttlFile, audioOut); } + // Also handles actually playing the RTTTL, needs to be called in loop bool isPlaying() { if (i2sRtttl != nullptr) { diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 093a24678..51dbcb7be 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -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) + 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() { diff --git a/src/SerialConsole.h b/src/SerialConsole.h index f1e636c9d..98577e4bc 100644 --- a/src/SerialConsole.h +++ b/src/SerialConsole.h @@ -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); }; diff --git a/src/buzz/BuzzerFeedbackThread.cpp b/src/buzz/BuzzerFeedbackThread.cpp index 12b30a705..afa8c96e2 100644 --- a/src/buzz/BuzzerFeedbackThread.cpp +++ b/src/buzz/BuzzerFeedbackThread.cpp @@ -5,7 +5,7 @@ BuzzerFeedbackThread *buzzerFeedbackThread; -BuzzerFeedbackThread::BuzzerFeedbackThread() : OSThread("BuzzerFeedback") +BuzzerFeedbackThread::BuzzerFeedbackThread() { if (inputBroker) inputObserver.observe(inputBroker); @@ -19,10 +19,6 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *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 switch (event->inputEvent) { case INPUT_BROKER_USER_PRESS: @@ -61,15 +57,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; -} +} \ No newline at end of file diff --git a/src/buzz/BuzzerFeedbackThread.h b/src/buzz/BuzzerFeedbackThread.h index dedea9860..7dc08ead5 100644 --- a/src/buzz/BuzzerFeedbackThread.h +++ b/src/buzz/BuzzerFeedbackThread.h @@ -4,7 +4,7 @@ #include "concurrency/OSThread.h" #include "input/InputBroker.h" -class BuzzerFeedbackThread : public concurrency::OSThread +class BuzzerFeedbackThread { CallbackObserver inputObserver = CallbackObserver(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; diff --git a/src/concurrency/OSThread.cpp b/src/concurrency/OSThread.cpp index 5aee03bbf..ce9a256b7 100644 --- a/src/concurrency/OSThread.cpp +++ b/src/concurrency/OSThread.cpp @@ -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) diff --git a/src/configuration.h b/src/configuration.h index dbd09235e..6968bb41e 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -117,6 +117,12 @@ along with this program. If not, see . #define SX126X_MAX_POWER 22 #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 #ifndef TX_GAIN_LORA #define TX_GAIN_LORA 0 diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index 3937bcf50..dcaa5d69b 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -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); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 7c4f4e05f..43b3fb8ab 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -852,24 +852,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; diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 988e7eb53..ff8cd20c5 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -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 === diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp index 32882f7ae..1d26e0758 100644 --- a/src/input/ButtonThread.cpp +++ b/src/input/ButtonThread.cpp @@ -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 INT32_MAX; } /* diff --git a/src/main.cpp b/src/main.cpp index 5a1b361b3..38c8e8ca5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1002,6 +1002,7 @@ void setup() config.pullupSense = INPUT_PULLUP; config.intRoutine = []() { UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); runASAP = true; BaseType_t higherWake = 0; mainDelay.interruptFromISR(&higherWake); @@ -1022,6 +1023,7 @@ void setup() touchConfig.pullupSense = pullup_sense; touchConfig.intRoutine = []() { TouchButtonThread->userButton.tick(); + TouchButtonThread->setIntervalFromNow(0); runASAP = true; BaseType_t higherWake = 0; mainDelay.interruptFromISR(&higherWake); @@ -1041,6 +1043,7 @@ void setup() cancelConfig.pullupSense = pullup_sense; cancelConfig.intRoutine = []() { CancelButtonThread->userButton.tick(); + CancelButtonThread->setIntervalFromNow(0); runASAP = true; BaseType_t higherWake = 0; mainDelay.interruptFromISR(&higherWake); @@ -1061,6 +1064,7 @@ void setup() backConfig.pullupSense = pullup_sense; backConfig.intRoutine = []() { BackButtonThread->userButton.tick(); + BackButtonThread->setIntervalFromNow(0); runASAP = true; BaseType_t higherWake = 0; mainDelay.interruptFromISR(&higherWake); @@ -1095,6 +1099,7 @@ void setup() userConfig.pullupSense = pullup_sense; userConfig.intRoutine = []() { UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); runASAP = true; BaseType_t higherWake = 0; mainDelay.interruptFromISR(&higherWake); @@ -1112,6 +1117,7 @@ void setup() userConfigNoScreen.pullupSense = pullup_sense; userConfigNoScreen.intRoutine = []() { UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); runASAP = true; BaseType_t higherWake = 0; mainDelay.interruptFromISR(&higherWake); @@ -1607,6 +1613,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); } } diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 3caee78f8..7ce106317 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -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. 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) { diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp index 4ae0a818e..dce436471 100644 --- a/src/mesh/NextHopRouter.cpp +++ b/src/mesh/NextHopRouter.cpp @@ -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. 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) { diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 97dfb3e52..b512ae675 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -701,7 +701,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 @@ -1667,9 +1667,6 @@ 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!"); } diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 31c68c302..b891ec89c 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -674,11 +674,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; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 171c383ba..7f17737e5 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -90,7 +90,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p) // 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 (int i = 0; i < nodeDB->getNumMeshNodes(); i++) { + for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); if (!node) continue; @@ -105,7 +105,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p) // Check 3: role check (moderate cost - multiple comparisons) if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER, - meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) { + meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) { 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) { // parsing was successful p->channel = chIndex; // change to store the index instead of the hash diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 3fc2562b3..785338483 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -80,6 +80,9 @@ template bool SX126xInterface::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 bool SX126xInterface::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, diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index f4c33bd79..db9dedaaf 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -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" */ diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h index ca4310bf1..d5573a1e2 100644 --- a/src/mesh/generated/meshtastic/channel.pb.h +++ b/src/mesh/generated/meshtastic/channel.pb.h @@ -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 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 0453ecad2..327568316 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -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. diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 7fab82ff7..b5b116137 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -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 diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 2f2934984..1279078b1 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -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; } } diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index d1d2d9ead..3d78d0dc9 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -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 } \ No newline at end of file diff --git a/src/modules/RangeTestModule.h b/src/modules/RangeTestModule.h index b632d343e..0512e70a8 100644 --- a/src/modules/RangeTestModule.h +++ b/src/modules/RangeTestModule.h @@ -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 diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 0c4e30785..5524765f3 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -53,7 +53,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread 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) diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 106900c48..ec6209487 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -224,7 +224,7 @@ extern struct portduino_config_struct { out << YAML::Key << "RF95_MAX_POWER" << YAML::Value << rf95_max_power; out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch; 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) out << YAML::Key << "USB_PID" << YAML::Value << YAML::Hex << lora_usb_pid; if (lora_usb_vid != 0x1A86) diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini index 1a448bc99..d0a250ad3 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -8,4 +8,3 @@ build_flags = -D 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 SX126X_MAX_POWER=11