diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml index 32e2c2c8b..a80619e90 100644 --- a/.github/workflows/stale_bot.yml +++ b/.github/workflows/stale_bot.yml @@ -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 diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 4e9de6a02..74b850b64 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,16 +8,16 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.471 - - renovate@41.130.1 + - checkov@3.2.473 + - renovate@41.132.5 - prettier@3.6.2 - 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.1 - - isort@6.0.1 + - ruff@0.13.2 + - isort@6.1.0 - markdownlint@0.45.0 - oxipng@9.1.5 - svgo@4.0.0 diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index 0d03dd349..3505f1940 100644 --- a/bin/org.meshtastic.meshtasticd.metainfo.xml +++ b/bin/org.meshtastic.meshtasticd.metainfo.xml @@ -87,6 +87,9 @@ + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.12 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.11 diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index e54d1586f..4a1887d9d 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -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") diff --git a/boards/heltec_wireless_tracker_v2.json b/boards/heltec_wireless_tracker_v2.json new file mode 100644 index 000000000..502954e69 --- /dev/null +++ b/boards/heltec_wireless_tracker_v2.json @@ -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" +} diff --git a/boards/r1-neo.json b/boards/r1-neo.json new file mode 100644 index 000000000..0383a2f48 --- /dev/null +++ b/boards/r1-neo.json @@ -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" +} diff --git a/debian/changelog b/debian/changelog index 15c8604f6..8bd053b25 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -meshtasticd (2.7.11.0) UNRELEASED; urgency=medium +meshtasticd (2.7.12.0) unstable; urgency=medium [ Austin Lane ] * Initial packaging @@ -7,4 +7,7 @@ meshtasticd (2.7.11.0) UNRELEASED; urgency=medium [ ] * GitHub Actions Automatic version bump - -- Wed, 24 Sep 2025 11:01:13 +0000 + [ GitHub Actions ] + * Version 2.7.12 + + -- GitHub Actions Wed, 01 Oct 2025 19:51:41 +0000 diff --git a/platformio.ini b/platformio.ini index f6c0f3867..b6d6733e3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -120,7 +120,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip + https://github.com/meshtastic/device-ui/archive/505ffadaa7a931df5dc8153229b719a07bbb028c.zip ; Common libs for environmental measurements in telemetry module [environmental_base] diff --git a/protobufs b/protobufs index 46b81e822..a1b8c3d17 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 46b81e822af1b8e408f437092337f129dee693e6 +Subproject commit a1b8c3d171445b2eebfd4b5bd1e4876f3bbed605 diff --git a/src/AudioThread.h b/src/AudioThread.h index cb1f804ac..df4892b6e 100644 --- a/src/AudioThread.h +++ b/src/AudioThread.h @@ -11,6 +11,11 @@ #include #include +#ifdef USE_XL9555 +#include "ExtensionIOXL9555.hpp" +extern ExtensionIOXL9555 io; +#endif + #define AUDIO_THREAD_INTERVAL_MS 100 class AudioThread : public concurrency::OSThread @@ -20,6 +25,9 @@ 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(); @@ -46,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) @@ -56,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: diff --git a/src/DisplayFormatters.cpp b/src/DisplayFormatters.cpp index 5193e1cb4..246cf0022 100644 --- a/src/DisplayFormatters.cpp +++ b/src/DisplayFormatters.cpp @@ -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; diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 51dbcb7be..fad0fb92f 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -86,7 +86,7 @@ int32_t SerialConsole::runOnce() #endif int32_t delay = runOncePart(); -#if defined(SERIAL_HAS_ON_RECEIVE) +#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); diff --git a/src/buzz/BuzzerFeedbackThread.cpp b/src/buzz/BuzzerFeedbackThread.cpp index afa8c96e2..7de6c0740 100644 --- a/src/buzz/BuzzerFeedbackThread.cpp +++ b/src/buzz/BuzzerFeedbackThread.cpp @@ -15,7 +15,8 @@ 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 } diff --git a/src/configuration.h b/src/configuration.h index 1b386ec17..e67f0b8e5 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 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 diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 170bef3a6..8ac503b83 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -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 diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 470a416c0..2e602338c 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -14,6 +14,7 @@ class ScanI2C SCREEN_ST7567, RTC_RV3028, RTC_PCF8563, + RTC_RX8130CE, CARDKB, TDECKKB, BBQ10KB, diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 01a630b52..6df3f8be1 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -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? diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 3bf2710fe..0487d1fea 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -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(); diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index da20e28eb..665a9aaa3 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -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 diff --git a/src/gps/RTC.h b/src/gps/RTC.h index eca17bf35..06dd34c16 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -4,6 +4,10 @@ #include "sys/time.h" #include +#ifdef RX8130CE_RTC +#include +#endif + enum RTCQuality { /// We haven't had our RTC set yet diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 1a9cf484b..31c211019 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -100,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; @@ -453,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 @@ -1458,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(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"); @@ -1490,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 + } } } diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 43b3fb8ab..c93e34545 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -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); } }; @@ -911,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); diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 2ccc0f861..ff8cd20c5 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -1105,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 1d26e0758..9f53b06f4 100644 --- a/src/input/ButtonThread.cpp +++ b/src/input/ButtonThread.cpp @@ -279,7 +279,7 @@ int32_t ButtonThread::runOnce() if (!userButton.isIdle() || waitingForLongPress) { return 50; } - return INT32_MAX; + return 100; // FIXME: Why can't we rely on interrupts and use INT32_MAX here? } /* diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index ef6d8df91..c588a9a0f 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -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 *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; -} \ No newline at end of file +} + +#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 diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 2cdfa2ae2..192bd7e8b 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -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 { CallbackObserver inputEventObserver = @@ -50,9 +59,22 @@ class InputBroker : public Observable InputBroker(); void registerSource(Observable *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; \ No newline at end of file diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp index 7d638dd71..7b43fa256 100644 --- a/src/input/RotaryEncoderImpl.cpp +++ b/src/input/RotaryEncoderImpl.cpp @@ -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 \ No newline at end of file diff --git a/src/input/RotaryEncoderImpl.h b/src/input/RotaryEncoderImpl.h index ae2a7c6fd..6f8e9fe5f 100644 --- a/src/input/RotaryEncoderImpl.h +++ b/src/input/RotaryEncoderImpl.h @@ -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, 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; diff --git a/src/main.cpp b/src/main.cpp index 38c8e8ca5..b0f086f14 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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); @@ -375,7 +381,7 @@ void setup() 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); @@ -793,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 && @@ -926,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); @@ -1602,6 +1600,10 @@ void loop() #endif service->loop(); +#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS) + if (inputBroker) + inputBroker->processInputEventQueue(); +#endif #if defined(LGFX_SDL) if (screen) { auto dispdev = screen->getDisplayDevice(); diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 3caee78f8..1d8ac247f 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) { @@ -81,9 +85,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) 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; } @@ -102,7 +105,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++; diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 30ad5945b..eaf71d294 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -59,7 +59,7 @@ 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 + // 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); diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h index 4829ddc1d..840184bbf 100644 --- a/src/mesh/LR11x0Interface.h +++ b/src/mesh/LR11x0Interface.h @@ -65,5 +65,7 @@ template 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 \ No newline at end of file diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index ef5380eb8..cbea85c62 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -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 diff --git a/src/mesh/MeshPacketQueue.h b/src/mesh/MeshPacketQueue.h index ea52eb5bf..3d3902c1e 100644 --- a/src/mesh/MeshPacketQueue.h +++ b/src/mesh/MeshPacketQueue.h @@ -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(); diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index a138ad1d1..1b2af082d 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -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)) { diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp index 4ae0a818e..0461d7eb6 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) { @@ -170,7 +174,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; @@ -208,7 +211,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 } @@ -221,7 +224,7 @@ 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); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index b512ae675..e3240462d 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -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 { @@ -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 } @@ -1672,6 +1693,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde } #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); diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index f6f1bc027..07f314415 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -710,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; diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h index 327e57900..ffd8ae008 100644 --- a/src/mesh/RF95Interface.h +++ b/src/mesh/RF95Interface.h @@ -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 \ No newline at end of file +#endif diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 31c68c302..88218e406 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -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; diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 0c5b6cd1a..6049a11cc 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -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 @@ -202,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. diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 3717e8780..2567d9e7f 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -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,11 +364,15 @@ 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++; + } } } @@ -411,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) { @@ -425,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 @@ -446,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 @@ -456,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 @@ -494,7 +506,7 @@ void RadioLibInterface::handleReceiveInterrupt() printPacket("Lora RX", mp); - airTime->logAirtime(RX_LOG, xmitMsec); + airTime->logAirtime(RX_LOG, rxMsec); deliverToReceiver(mp); } diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 3444b1a2c..833c88710 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -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,6 +221,36 @@ 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 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="; /** diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index cca838ff0..00066a7a3 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -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; } \ No newline at end of file diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 2cf10fb99..33121de6b 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -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); }; \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 7f17737e5..60637cbd1 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -198,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) @@ -399,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); diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 92a5a06e5..10a3771a7 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -125,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: /** diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 3fc2562b3..c60ef3a80 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -52,7 +52,7 @@ template bool SX126xInterface::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 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, @@ -349,7 +352,7 @@ template bool SX126xInterface::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 bool SX126xInterface::sleep() /** Some boards require GPIO control of tx vs rx paths */ template void SX126xInterface::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); diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index dc7024daa..b8f16ac6d 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -72,6 +72,8 @@ template 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); diff --git a/src/mesh/SX128xInterface.h b/src/mesh/SX128xInterface.h index bba31dab4..acdcbbb27 100644 --- a/src/mesh/SX128xInterface.h +++ b/src/mesh/SX128xInterface.h @@ -67,4 +67,6 @@ template 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); } }; diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index bc0b780b9..7cc896292 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -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" */ 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/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h index d9eb90773..b99fb10b9 100644 --- a/src/mesh/generated/meshtastic/device_ui.pb.h +++ b/src/mesh/generated/meshtastic/device_ui.pb.h @@ -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) */ 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/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 6292ce070..d8d2f2e8a 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -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. ------------------------------------------------------------------------------------------------------------------------------------------ */ diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 9af095e78..a8ca96e95 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -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 diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 79ea7bc0c..d300ff53b 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -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; diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 1279078b1..2346cd299 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -442,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); @@ -453,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"); @@ -509,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); @@ -520,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); @@ -531,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 diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 19cf9eb7b..f667f7be9 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -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, diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index bd8997493..e477574dd 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -112,204 +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 - 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(); diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 276a11b3a..9b94b3933 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -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); diff --git a/src/modules/NodeInfoModule.h b/src/modules/NodeInfoModule.h index c1fb9ccce..572b81700 100644 --- a/src/modules/NodeInfoModule.h +++ b/src/modules/NodeInfoModule.h @@ -30,6 +30,9 @@ class NodeInfoModule : public ProtobufModule, 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; diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index e7e92c79a..05173983c 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -42,17 +42,19 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh meshtastic_MeshPacket *RoutingModule::allocReply() { - if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) - return NULL; assert(currentRequest); return NULL; } -void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit) +void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit, + bool ackWantsAck) { auto p = allocAckNak(err, to, idFrom, chIndex, hopLimit); + // Allow the caller to set want_ack on this ACK packet if it's important that the ACK be delivered reliably + p->want_ack = ackWantsAck; + router->sendLocal(p); // we sometimes send directly to the local node } diff --git a/src/modules/RoutingModule.h b/src/modules/RoutingModule.h index c047f6e29..a4e0679d0 100644 --- a/src/modules/RoutingModule.h +++ b/src/modules/RoutingModule.h @@ -13,8 +13,8 @@ class RoutingModule : public ProtobufModule */ RoutingModule(); - virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, - uint8_t hopLimit = 0); + virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0, + bool ackWantsAck = false); // Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit); diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 45362255c..066b9361d 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -26,7 +26,6 @@ int32_t DeviceTelemetryModule::runOnce() Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval, default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() && - config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN && moduleConfig.telemetry.device_telemetry_enabled) { sendTelemetry(); @@ -45,10 +44,6 @@ int32_t DeviceTelemetryModule::runOnce() bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) { - // Don't worry about storing telemetry in NodeDB if we're a repeater - if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) - return false; - if (t->which_variant == meshtastic_Telemetry_device_metrics_tag) { #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) const char *sender = getSenderShortName(mp); @@ -127,6 +122,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry() telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad; telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad; telemetry.variant.local_stats.num_tx_relay = RadioLibInterface::instance->txRelay; + telemetry.variant.local_stats.num_tx_dropped = RadioLibInterface::instance->txDrop; } #ifdef ARCH_PORTDUINO if (SimRadio::instance) { @@ -134,6 +130,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry() telemetry.variant.local_stats.num_packets_rx = SimRadio::instance->rxGood + SimRadio::instance->rxBad; telemetry.variant.local_stats.num_packets_rx_bad = SimRadio::instance->rxBad; telemetry.variant.local_stats.num_tx_relay = SimRadio::instance->txRelay; + telemetry.variant.local_stats.num_tx_dropped = SimRadio::instance->txDrop; } #else telemetry.variant.local_stats.heap_total_bytes = memGet.getHeapSize(); diff --git a/src/modules/Telemetry/HostMetrics.cpp b/src/modules/Telemetry/HostMetrics.cpp index dcde495a2..577132006 100644 --- a/src/modules/Telemetry/HostMetrics.cpp +++ b/src/modules/Telemetry/HostMetrics.cpp @@ -22,10 +22,6 @@ int32_t HostMetricsModule::runOnce() bool HostMetricsModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) { - // Don't worry about storing telemetry in NodeDB if we're a repeater - if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) - return false; - if (t->which_variant == meshtastic_Telemetry_host_metrics_tag) { #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) const char *sender = getSenderShortName(mp); diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index 8f69c504a..fc2cc232b 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -419,6 +419,9 @@ bool TraceRouteModule::startTraceRoute(NodeNum node) p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP; p->decoded.want_response = true; + // Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo) + p->want_ack = true; + // Manually encode the RouteDiscovery payload p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req); @@ -532,6 +535,9 @@ void TraceRouteModule::launch(NodeNum node) p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP; p->decoded.want_response = true; + // Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo) + p->want_ack = true; + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req); diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 6b658c2a3..53b23124d 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -201,6 +201,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4 #elif defined(M5STACK_UNITC6L) #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L +#elif defined(HELTEC_WIRELESS_TRACKER_V2) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 #endif // ----------------------------------------------------------------------------- diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index b457c35b7..79eef8f76 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -335,7 +335,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke meshtastic::BluetoothStatus newStatus(textkey); bluetoothStatus->updateStatus(&newStatus); -#if !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus +#if HAS_SCREEN && \ + !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus if (screen) { screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { char btPIN[16] = "888888"; diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index c9938062e..cee0e5a10 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -55,6 +55,8 @@ #define HW_VENDOR meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER #elif defined(NOMADSTAR_METEOR_PRO) #define HW_VENDOR meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO +#elif defined(R1_NEO) +#define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO // MAke sure all custom RAK4630 boards are defined before the generic RAK4630 #elif defined(RAK4630) #define HW_VENDOR meshtastic_HardwareModel_RAK4631 @@ -149,3 +151,6 @@ // No serial ports on this board - ONLY use segger in memory console #define USE_SEGGER #endif + +// Detect if running in ISR context (ARM Cortex-M4) +#define xPortInIsrContext() ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) == 0 ? pdFALSE : pdTRUE) diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp index cea1eab3a..6e7fe24cb 100644 --- a/src/platform/portduino/SimRadio.cpp +++ b/src/platform/portduino/SimRadio.cpp @@ -13,7 +13,12 @@ ErrorCode SimRadio::send(meshtastic_MeshPacket *p) { printPacket("enqueuing for send", p); - 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); @@ -182,7 +187,7 @@ void SimRadio::onNotify(uint32_t notification) assert(txp); startSend(txp); // Packet has been sent, count it toward our TX airtime utilization. - uint32_t xmitMsec = getPacketTime(txp); + uint32_t xmitMsec = RadioInterface::getPacketTime(txp); airTime->logAirtime(TX_LOG, xmitMsec); notifyLater(xmitMsec, ISR_TX, false); // Model the time it is busy sending @@ -252,7 +257,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p) if (isActivelyReceiving()) { LOG_WARN("Collision detected, dropping current and previous packet!"); rxBad++; - airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket)); + airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket, true)); packetPool.release(receivingPacket); receivingPacket = nullptr; return; @@ -270,7 +275,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p) } isReceiving = true; receivingPacket = packetPool.allocCopy(*p); - uint32_t airtimeMsec = getPacketTime(p); + uint32_t airtimeMsec = getPacketTime(p, true); notifyLater(airtimeMsec, ISR_RX, false); // Model the time it is busy receiving #else isReceiving = true; @@ -311,7 +316,7 @@ void SimRadio::handleReceiveInterrupt() printPacket("Lora RX", mp); - airTime->logAirtime(RX_LOG, getPacketTime(mp)); + airTime->logAirtime(RX_LOG, RadioInterface::getPacketTime(mp, true)); deliverToReceiver(mp); } @@ -332,4 +337,29 @@ int16_t SimRadio::readData(uint8_t *data, size_t len) } return state; +} + +/** + * 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 SimRadio::getPacketTime(uint32_t pl, bool received) +{ + 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; } \ No newline at end of file diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h index d8b53739f..6f80989da 100644 --- a/src/platform/portduino/SimRadio.h +++ b/src/platform/portduino/SimRadio.h @@ -52,6 +52,7 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr * Debugging counts */ uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0; + uint16_t txDrop = 0; protected: /// are _trying_ to receive a packet currently (note - we might just be waiting for one) @@ -88,6 +89,8 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr /** * If a send was in progress finish it and return the buffer to the pool */ void completeSending(); + + virtual uint32_t getPacketTime(uint32_t pl, bool received = false) override; }; extern SimRadio *simRadio; \ No newline at end of file diff --git a/src/platform/rp2xx0/architecture.h b/src/platform/rp2xx0/architecture.h index 506c19c83..0c168ceee 100644 --- a/src/platform/rp2xx0/architecture.h +++ b/src/platform/rp2xx0/architecture.h @@ -35,4 +35,7 @@ #define HW_VENDOR meshtastic_HardwareModel_RP2040_FEATHER_RFM95 #elif defined(PRIVATE_HW) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW -#endif \ No newline at end of file +#endif + +// Detect if running in ISR context (ARM Cortex-M33 / RISC-V) +#define xPortInIsrContext() (__get_current_exception() == 0 ? pdFALSE : pdTRUE) diff --git a/src/power.h b/src/power.h index e96f5b022..23eb95064 100644 --- a/src/power.h +++ b/src/power.h @@ -34,6 +34,8 @@ #define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300 #elif defined(SEEED_SOLAR_NODE) #define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786 +#elif defined(R1_NEO) +#define OCV_ARRAY 4330, 4292, 4254, 4216, 4178, 4140, 4102, 4064, 4026, 3988, 3950 #else // LiIon #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 0f8931292..c6efb0efd 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -532,8 +532,7 @@ void enableModemSleep() bool shouldLoraWake(uint32_t msecToWake) { - return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || - config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER); + return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER); } void enableLoraInterrupt() @@ -554,7 +553,7 @@ void enableLoraInterrupt() gpio_pullup_en((gpio_num_t)LORA_CS); #endif -#ifdef HELTEC_V4 +#if defined(USE_GC1109_PA) gpio_pullup_en((gpio_num_t)LORA_PA_POWER); gpio_pullup_en((gpio_num_t)LORA_PA_EN); gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN); diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp index 32d81f6b4..ede3d22b7 100644 --- a/test/test_mqtt/MQTT.cpp +++ b/test/test_mqtt/MQTT.cpp @@ -83,8 +83,8 @@ class MockNodeDB : public NodeDB class MockRoutingModule : public RoutingModule { public: - void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, - uint8_t hopLimit = 0) override + void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0, + bool ackWantsAck = false) override { ackNacks_.emplace_back(err, to, idFrom, chIndex, hopLimit); } diff --git a/userPrefs.jsonc b/userPrefs.jsonc index f6f3ef995..464eb968c 100644 --- a/userPrefs.jsonc +++ b/userPrefs.jsonc @@ -21,7 +21,7 @@ // "USERPREFS_CONFIG_LORA_REGION": "meshtastic_Config_LoRaConfig_RegionCode_US", // "USERPREFS_CONFIG_OWNER_LONG_NAME": "My Long Name", // "USERPREFS_CONFIG_OWNER_SHORT_NAME": "MLN", - // "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, LOST AND FOUND, and REPEATER roles are restricted. + // "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, and LOST AND FOUND roles are restricted. // "USERPREFS_EVENT_MODE": "1", // "USERPREFS_FIRMWARE_EDITION": "meshtastic_FirmwareEdition_BURNING_MAN", // "USERPREFS_FIXED_BLUETOOTH": "121212", 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 diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h index 2b6b7af3d..1c9516e39 100644 --- a/variants/esp32s3/heltec_v4/variant.h +++ b/variants/esp32s3/heltec_v4/variant.h @@ -37,6 +37,7 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define USE_GC1109_PA // We have a GC1109 power amplifier+attenuator #define LORA_PA_POWER 7 // power en #define LORA_PA_EN 2 #define LORA_PA_TX_EN 46 // enable tx diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h new file mode 100644 index 000000000..61c319109 --- /dev/null +++ b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h @@ -0,0 +1,71 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "soc/soc_caps.h" +#include + +#define DISPLAY_HEIGHT 80 +#define DISPLAY_WIDTH 160 + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +static const uint8_t LED_BUILTIN = 18; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 5; +static const uint8_t SCL = 6; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 3; +static const uint8_t LED = 18; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini new file mode 100644 index 000000000..4872561db --- /dev/null +++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini @@ -0,0 +1,14 @@ +[env:heltec-wireless-tracker-v2] +extends = esp32s3_base +board = heltec_wireless_tracker_v2 +board_build.partitions = default_8MB.csv +upload_protocol = esptool + +build_flags = + ${esp32s3_base.build_flags} + -I variants/esp32s3/heltec_wireless_tracker_v2 + -D HELTEC_WIRELESS_TRACKER_V2 + -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. +lib_deps = + ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX@^1.2.0 diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h new file mode 100644 index 000000000..9ac064ea2 --- /dev/null +++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h @@ -0,0 +1,78 @@ +#define LED_PIN 18 + +#define _VARIANT_HELTEC_WIRELESS_TRACKER + +// I2C +#define I2C_SDA SDA +#define I2C_SCL SCL + +// ST7735S TFT LCD +#define ST7735S 1 // there are different (sub-)versions of ST7735 +#define ST7735_CS 38 +#define ST7735_RS 40 // DC +#define ST7735_SDA 42 // MOSI +#define ST7735_SCK 41 +#define ST7735_RESET 39 +#define ST7735_MISO -1 +#define ST7735_BUSY -1 +#define TFT_BL 21 +#define ST7735_SPI_HOST SPI3_HOST +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define SCREEN_ROTATE +#define TFT_HEIGHT DISPLAY_WIDTH +#define TFT_WIDTH DISPLAY_HEIGHT +#define TFT_OFFSET_X 24 +#define TFT_OFFSET_Y 0 +#define TFT_INVERT false +#define SCREEN_TRANSITION_FRAMERATE 3 // fps +#define DISPLAY_FORCE_SMALL_FONTS + +#define VEXT_ENABLE 3 // active HIGH - powers the GPS, GPS LNA and OLED +#define VEXT_ON_VALUE HIGH +#define BUTTON_PIN 0 + +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider +#define ADC_MULTIPLIER 4.9 * 1.045 +#define ADC_CTRL 2 // active HIGH, powers the voltage divider. +#define ADC_USE_PULLUP // Use internal pullup/pulldown instead of actively driving the output + +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 33 +#define GPS_TX_PIN 34 +#define PIN_GPS_RESET 35 +#define PIN_GPS_PPS 36 +// #define PIN_GPS_EN 3 // Uncomment to power off the GPS with triple-click on Tracker v2, though we'll also lose the +// display. + +#define GPS_RESET_MODE LOW +#define GPS_UC6580 +#define GPS_BAUDRATE 115200 + +#define USE_SX1262 +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TCXO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define USE_GC1109_PA // We have a GC1109 power amplifier+attenuator +#define LORA_PA_POWER 7 // power en +#define LORA_PA_EN 4 +#define LORA_PA_TX_EN 46 // enable tx \ No newline at end of file diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index 312d46259..9800161bb 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -15,7 +15,7 @@ build_flags = ${esp32s3_base.build_flags} -D SDCARD_USE_SPI1 -D ENABLE_ROTARY_PULLUP -D ENABLE_BUTTON_PULLUP - -D HALF_STEP + -D ROTARY_BUXTRONICS lib_deps = ${esp32s3_base.lib_deps} lovyan03/LovyanGFX@1.2.7 @@ -26,7 +26,7 @@ lib_deps = ${esp32s3_base.lib_deps} lewisxhe/SensorLib@0.3.1 https://github.com/pschatzmann/arduino-audio-driver/archive/refs/tags/v0.1.3.zip https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip - https://github.com/mverch67/RotaryEncoder/archive/25a59d5745a6645536f921427d80b08e78f886d4.zip + https://github.com/mverch67/RotaryEncoder/archive/da958a21389cbcd485989705df602a33e092dd88.zip [env:tlora-pager-tft] board_level = extra diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini index 5c1047aae..c6a5a7399 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini +++ b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini @@ -6,7 +6,8 @@ board = gat562_mesh_trial_tracker board_check = true build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/gat562_mesh_trial_tracker - -D GAT562_MESH_TRIAL_TRACKER + ;-D GAT562_MESH_TRIAL_TRACKER + -D PRIVATE_HW -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 diff --git a/variants/nrf52840/r1-neo/platformio.ini b/variants/nrf52840/r1-neo/platformio.ini new file mode 100644 index 000000000..6feb55dc9 --- /dev/null +++ b/variants/nrf52840/r1-neo/platformio.ini @@ -0,0 +1,19 @@ +; The R1 Neo board +[env:r1-neo] +extends = nrf52840_base +board = r1-neo +board_check = true +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/r1-neo + -D R1_NEO + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/r1-neo> + + +lib_deps = + ${nrf52840_base.lib_deps} + ${networking_base.lib_deps} + https://github.com/RAKWireless/RAK13800-W5100S/archive/1.0.2.zip + rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 + artronshop/ArtronShop_RX8130CE@1.0.0 diff --git a/variants/nrf52840/r1-neo/variant.cpp b/variants/nrf52840/r1-neo/variant.cpp new file mode 100644 index 000000000..f87c041aa --- /dev/null +++ b/variants/nrf52840/r1-neo/variant.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + // pinMode(PIN_3V3_EN, OUTPUT); + // digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h new file mode 100644 index 000000000..901e993e3 --- /dev/null +++ b/variants/nrf52840/r1-neo/variant.h @@ -0,0 +1,149 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_R1NEO_ +#define _VARIANT_R1NEO_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (32 + 4) // P1.04 Controls Green LED +#define PIN_LED2 (28) // P0.28 Controls Blue LED + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +// Button +#define PIN_BUTTON1 (26) +#define BUTTON_ACTIVE_LOW 0 +#define BUTTON_ACTIVE_PULLUP 0 +#define BUTTON_SENSE_TYPE INPUT_SENSE_HIGH + +#define ADC_RESOLUTION 14 + +// Serial for GPS +#define PIN_SERIAL1_RX (25) +#define PIN_SERIAL1_TX (24) + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +static const uint8_t SS = 42; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +// R1 Neo Extras +#define DCDC_EN_HOLD (13) // P0.13 Keeps DCDC alive after user button is pressed +#define NRF_ON (29) // P0.29 Tells IO controller device is on + +// RAKRGB +#define HAS_NCP5623 + +#define HAS_SCREEN 0 + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (19) // P0.19 RTC_SDA +#define PIN_WIRE_SCL (20) // P0.20 RTC_SCL + +#define PIN_BUZZER (0 + 3) // P0.03 + +#define USE_SX1262 +#define SX126X_CS (42) +#define SX126X_DIO1 (47) +#define SX126X_BUSY (46) +#define SX126X_RESET (38) +#define SX126X_POWER_EN (37) + +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Testing USB detection +#define NRF_APM + +#define PIN_GPS_EN (32 + 1) // P1.01 +#define PIN_GPS_PPS (2) // P0.02 Pulse per second input from the GPS + +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +// Battery +#define BATTERY_PIN (0 + 31) // P0.31 ADC_VBAT +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.73 + +#define HAS_RTC 1 + +#define RX8130CE_RTC 0x32 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index 2e2cdce72..0748f6d48 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -140,11 +140,12 @@ static const uint8_t A0 = PIN_A0; #define HAS_GPS 1 // #define PIN_GPS_REINIT (32 + 5) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K -#define PIN_GPS_STANDBY (32 + 10) // An output to wake GPS, low means allow sleep, high means force wake -// Seems to be missing on this new board -#define PIN_GPS_PPS (0 + 29) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 15) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 13) // This is for bits going TOWARDS the GPS +#define PIN_GPS_EN (32 + 11) // GPS power +#define GPS_EN_ACTIVE 1 +#define PIN_GPS_STANDBY (32 + 13) // wakeup pin +#define PIN_GPS_PPS (32 + 15) +#define GPS_TX_PIN (32 + 10) // L76K module RX PIN +#define GPS_RX_PIN (0 + 29) // L76K module TX PIN #define GPS_THREAD_INTERVAL 50 @@ -204,4 +205,4 @@ static const uint8_t A0 = PIN_A0; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif diff --git a/version.properties b/version.properties index ce1205f2b..5c84a8e65 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 11 +build = 12