diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 350ca290c..f6c1fd80c 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -11,11 +11,6 @@ runs: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - name: Uncomment build epoch - shell: bash - run: | - sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini - - name: Install dependencies shell: bash run: | diff --git a/bin/build-firmware.sh b/bin/build-firmware.sh index fdd7caa11..7bd19aaa9 100644 --- a/bin/build-firmware.sh +++ b/bin/build-firmware.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini - export PIP_BREAK_SYSTEM_PACKAGES=1 if (echo $2 | grep -q "esp32"); then diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index fc1b4bc2e..e54d1586f 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -6,6 +6,8 @@ from os.path import join import subprocess import json import re +import time +from datetime import datetime from readprops import readProps @@ -125,11 +127,16 @@ for pref in userPrefs: pref_flags.append("-D" + pref + "=" + env.StringifyMacro(userPrefs[pref]) + "") # General options that are passed to the C and C++ compilers +# Calculate unix epoch for current day (midnight) +current_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) +build_epoch = int(current_date.timestamp()) + flags = [ "-DAPP_VERSION=" + verObj["long"], "-DAPP_VERSION_SHORT=" + verObj["short"], "-DAPP_ENV=" + env.get("PIOENV"), "-DAPP_REPO=" + repo_owner, + "-DBUILD_EPOCH=" + str(build_epoch), ] + pref_flags print ("Using flags:") diff --git a/platformio.ini b/platformio.ini index 6d56870e8..a73ae8040 100644 --- a/platformio.ini +++ b/platformio.ini @@ -53,7 +53,7 @@ build_flags = -Wno-missing-field-initializers -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware -DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1 -D MAX_THREADS=40 ; As we've split modules, we have more threads to manage - #-DBUILD_EPOCH=$UNIX_TIME + #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now #-D OLED_PL=1 monitor_speed = 115200 @@ -117,7 +117,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/233d18ef42e9d189f90fdfe621f0cd7edff2d221.zip + https://github.com/meshtastic/device-ui/archive/3677476c8a823ee85056b5fb1d146a3e193f8276.zip ; Common libs for environmental measurements in telemetry module [environmental_base] diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index a34710eb0..98bbe0f72 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -2,6 +2,12 @@ #include "configuration.h" +// Forward declarations +#if defined(DEBUG_HEAP) +class MemGet; +extern MemGet memGet; +#endif + // DEBUG LED #ifndef LED_STATE_ON #define LED_STATE_ON 1 @@ -23,6 +29,7 @@ #define MESHTASTIC_LOG_LEVEL_ERROR "ERROR" #define MESHTASTIC_LOG_LEVEL_CRIT "CRIT " #define MESHTASTIC_LOG_LEVEL_TRACE "TRACE" +#define MESHTASTIC_LOG_LEVEL_HEAP "HEAP" #include "SerialConsole.h" @@ -62,6 +69,25 @@ #endif #endif +#if defined(DEBUG_HEAP) +#define LOG_HEAP(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__) + +// Macro-based heap debugging +#define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap(); +#define DEBUG_HEAP_AFTER(context, ptr) \ + do { \ + auto heapAfter = memGet.getFreeHeap(); \ + if (heapBefore != heapAfter) { \ + LOG_HEAP("Alloc in %s pointer 0x%x, size: %u, free: %u", context, ptr, heapBefore - heapAfter, heapAfter); \ + } \ + } while (0) + +#else +#define LOG_HEAP(...) +#define DEBUG_HEAP_BEFORE +#define DEBUG_HEAP_AFTER(context, ptr) +#endif + /// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic extern "C" void logLegacy(const char *level, const char *fmt, ...); diff --git a/src/Power.cpp b/src/Power.cpp index 06c6a9089..7de82b8d6 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -851,9 +851,9 @@ void Power::readPowerStatus() running++; } } - LOG_DEBUG(threadlist); - LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(), - memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false)); + LOG_HEAP(threadlist); + LOG_HEAP("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(), + memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false)); lastheap = memGet.getFreeHeap(); } #ifdef DEBUG_HEAP_MQTT diff --git a/src/concurrency/OSThread.cpp b/src/concurrency/OSThread.cpp index d9bb901b2..5aee03bbf 100644 --- a/src/concurrency/OSThread.cpp +++ b/src/concurrency/OSThread.cpp @@ -86,9 +86,9 @@ void OSThread::run() #ifdef DEBUG_HEAP auto newHeap = memGet.getFreeHeap(); if (newHeap < heap) - LOG_DEBUG("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap); + LOG_HEAP("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap); if (heap < newHeap) - LOG_DEBUG("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap); + LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap); #endif runned(); diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 88984a890..d4e9076d9 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1,5 +1,4 @@ #include // Include for strstr -#include #include #include "configuration.h" @@ -1370,34 +1369,42 @@ GnssModel_t GPS::probe(int serialSpeed) GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector &responseMap) { - String response = ""; + char response[256] = {0}; // Fixed buffer instead of String + uint16_t responseLen = 0; unsigned long start = millis(); while (millis() - start < timeout) { if (_serial_gps->available()) { - response += (char)_serial_gps->read(); + char c = _serial_gps->read(); - if (response.endsWith(",") || response.endsWith("\r\n")) { + // Add char to buffer if there's space + if (responseLen < sizeof(response) - 1) { + response[responseLen++] = c; + response[responseLen] = '\0'; + } + + if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) { #ifdef GPS_DEBUG - LOG_DEBUG(response.c_str()); + LOG_DEBUG(response); #endif // check if we can see our chips for (const auto &chipInfo : responseMap) { - if (strstr(response.c_str(), chipInfo.detectionString.c_str()) != nullptr) { + if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) { LOG_INFO("%s detected", chipInfo.chipName.c_str()); return chipInfo.driver; } } } - if (response.endsWith("\r\n")) { - response.trim(); - response = ""; // Reset the response string for the next potential message + if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') { + // Reset the response buffer for the next potential message + responseLen = 0; + response[0] = '\0'; } } } #ifdef GPS_DEBUG - LOG_DEBUG(response.c_str()); + LOG_DEBUG(response); #endif - return GNSS_MODEL_UNKNOWN; // Return empty string on timeout + return GNSS_MODEL_UNKNOWN; // Return unknown on timeout } GPS *GPS::createGps() diff --git a/src/memGet.cpp b/src/memGet.cpp index e8cd177dd..14e614014 100644 --- a/src/memGet.cpp +++ b/src/memGet.cpp @@ -88,4 +88,16 @@ uint32_t MemGet::getPsramSize() #else return 0; #endif +} + +void displayPercentHeapFree() +{ + uint32_t freeHeap = memGet.getFreeHeap(); + uint32_t totalHeap = memGet.getHeapSize(); + if (totalHeap == 0 || totalHeap == UINT32_MAX) { + LOG_INFO("Heap size unavailable"); + return; + } + int percent = (int)((freeHeap * 100) / totalHeap); + LOG_INFO("Heap free: %d%% (%u/%u bytes)", percent, freeHeap, totalHeap); } \ No newline at end of file diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h index c4af3c4ac..ea7c8f583 100644 --- a/src/mesh/MemoryPool.h +++ b/src/mesh/MemoryPool.h @@ -84,6 +84,8 @@ template class MemoryDynamic : public Allocator virtual void release(T *p) override { assert(p); + LOG_HEAP("Freeing 0x%x", p); + free(p); } diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 22fcec663..c5748a560 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -100,7 +100,6 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets auto ourNodeNum = nodeDB->getNodeNum(); bool toUs = isBroadcast(mp.to) || isToUs(&mp); - bool fromUs = mp.from == ourNodeNum; for (auto i = modules->begin(); i != modules->end(); ++i) { auto &pi = **i; diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 2cc4197c1..607766ab6 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -61,8 +61,10 @@ Allocator &queueStatusPool = staticQueueStatusPool; #include "Router.h" MeshService::MeshService() - : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE), - toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2) +#ifdef ARCH_PORTDUINO + : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_QUEUESTATUS_TOPHONE), + toPhoneMqttProxyQueue(MAX_RX_MQTTPROXY_TOPHONE), toPhoneClientNotificationQueue(MAX_RX_NOTIFICATION_TOPHONE) +#endif { lastQueueStatus = {0, 0, 16, 0}; } @@ -191,8 +193,10 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p) // (so we update our nodedb for the local node) // Send the packet into the mesh - - sendToMesh(packetPool.allocCopy(p), RX_SRC_USER); + DEBUG_HEAP_BEFORE; + auto a = packetPool.allocCopy(p); + DEBUG_HEAP_AFTER("MeshService::handleToRadio", a); + sendToMesh(a, RX_SRC_USER); bool loopback = false; // if true send any packet the phone sends back itself (for testing) if (loopback) { @@ -248,7 +252,11 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh } if ((res == ERRNO_OK || res == ERRNO_SHOULD_RELEASE) && ccToPhone) { // Check if p is not released in case it couldn't be sent - sendToPhone(packetPool.allocCopy(*p)); + DEBUG_HEAP_BEFORE; + auto a = packetPool.allocCopy(*p); + DEBUG_HEAP_AFTER("MeshService::sendToMesh", a); + + sendToPhone(a); } // Router may ask us to release the packet if it wasn't sent diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index f7d79366e..5d074368f 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -9,7 +9,12 @@ #include "MeshRadio.h" #include "MeshTypes.h" #include "Observer.h" +#ifdef ARCH_PORTDUINO #include "PointerQueue.h" +#else +#include "StaticPointerQueue.h" +#endif +#include "mesh-pb-constants.h" #if defined(ARCH_PORTDUINO) #include "../platform/portduino/SimRadio.h" #endif @@ -37,16 +42,32 @@ class MeshService /// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure /// we never hang because android hasn't been there in a while /// FIXME - save this to flash on deep sleep +#ifdef ARCH_PORTDUINO PointerQueue toPhoneQueue; +#else + StaticPointerQueue toPhoneQueue; +#endif // keep list of QueueStatus packets to be send to the phone +#ifdef ARCH_PORTDUINO PointerQueue toPhoneQueueStatusQueue; +#else + StaticPointerQueue toPhoneQueueStatusQueue; +#endif // keep list of MqttClientProxyMessages to be send to the client for delivery +#ifdef ARCH_PORTDUINO PointerQueue toPhoneMqttProxyQueue; +#else + StaticPointerQueue toPhoneMqttProxyQueue; +#endif // keep list of ClientNotifications to be send to the client (phone) +#ifdef ARCH_PORTDUINO PointerQueue toPhoneClientNotificationQueue; +#else + StaticPointerQueue toPhoneClientNotificationQueue; +#endif // This holds the last QueueStatus send meshtastic_QueueStatus lastQueueStatus; diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp index 7ceca2195..db3d62038 100644 --- a/src/mesh/NextHopRouter.cpp +++ b/src/mesh/NextHopRouter.cpp @@ -175,12 +175,18 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key) config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) { // remove the 'original' (identified by originator and packet->id) from the txqueue and free it cancelSending(getFrom(p), p->id); - // now free the pooled copy for retransmission too - packetPool.release(p); } } + + // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't + // get scheduled again. (This is the core of stopRetransmission.) auto numErased = pending.erase(key); assert(numErased == 1); + + // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call + // to startRetransmission. + packetPool.release(p); + return true; } else return false; diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index a3a8a2087..9fb1b589f 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -100,6 +100,7 @@ void PhoneAPI::close() config_nonce = 0; config_state = 0; pauseBluetoothLogging = false; + heartbeatReceived = false; } } diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index e9ceeaef1..6d098b669 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -2,6 +2,7 @@ #include "Default.h" #include "MeshTypes.h" #include "configuration.h" +#include "memGet.h" #include "mesh-pb-constants.h" #include "modules/NodeInfoModule.h" #include "modules/RoutingModule.h" @@ -21,8 +22,10 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p) if (p->hop_limit == 0) { p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); } - + DEBUG_HEAP_BEFORE; auto copy = packetPool.allocCopy(*p); + DEBUG_HEAP_AFTER("ReliableRouter::send", copy); + startRetransmission(copy, NUM_RELIABLE_RETX); } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index c7e32c4a1..44d09637f 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -275,7 +275,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) // If the packet is not yet encrypted, do so now if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it + + DEBUG_HEAP_BEFORE; meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p); + DEBUG_HEAP_AFTER("Router::send", p_decoded); auto encodeResult = perhapsEncode(p); if (encodeResult != meshtastic_Routing_Error_NONE) { @@ -607,8 +610,11 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) bool skipHandle = false; // Also, we should set the time from the ISR and it should have msec level resolution p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone + // Store a copy of encrypted packet for MQTT + DEBUG_HEAP_BEFORE; meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p); + DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted); // Take those raw bytes and convert them back into a well structured protobuf we can understand auto decodedState = perhapsDecode(p); @@ -656,7 +662,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) // call modules here // If this could be a spoofed packet, don't let the modules see it. - if (!skipHandle && p->from != nodeDB->getNodeNum()) { + if (!skipHandle) { MeshModule::callModules(*p, src); #if !MESHTASTIC_EXCLUDE_MQTT @@ -670,8 +676,6 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) !isFromUs(p) && mqtt) mqtt->onSend(*p_encrypted, *p, p->channel); #endif - } else if (p->from == nodeDB->getNodeNum() && !skipHandle) { - MeshModule::callModules(*p, src); } packetPool.release(p_encrypted); // Release the encrypted packet diff --git a/src/mesh/StaticPointerQueue.h b/src/mesh/StaticPointerQueue.h new file mode 100644 index 000000000..398ee450c --- /dev/null +++ b/src/mesh/StaticPointerQueue.h @@ -0,0 +1,77 @@ +#pragma once + +#include "concurrency/OSThread.h" +#include "freertosinc.h" +#include + +/** + * A static circular buffer queue for pointers. + * This provides the same interface as PointerQueue but uses a statically allocated + * buffer instead of dynamic allocation. + */ +template class StaticPointerQueue +{ + static_assert(MaxElements > 0, "MaxElements must be greater than 0"); + + T *buffer[MaxElements]; + int head = 0; + int tail = 0; + int count = 0; + concurrency::OSThread *reader = nullptr; + + public: + StaticPointerQueue() + { + // Initialize all buffer elements to nullptr to silence warnings and ensure clean state + for (int i = 0; i < MaxElements; i++) { + buffer[i] = nullptr; + } + } + + int numFree() const { return MaxElements - count; } + + bool isEmpty() const { return count == 0; } + + int numUsed() const { return count; } + + bool enqueue(T *x, TickType_t maxWait = portMAX_DELAY) + { + if (count >= MaxElements) { + return false; // Queue is full + } + + if (reader) { + reader->setInterval(0); + concurrency::mainDelay.interrupt(); + } + + buffer[tail] = x; + tail = (tail + 1) % MaxElements; + count++; + return true; + } + + bool dequeue(T **p, TickType_t maxWait = portMAX_DELAY) + { + if (count == 0) { + return false; // Queue is empty + } + + *p = buffer[head]; + head = (head + 1) % MaxElements; + count--; + return true; + } + + // returns a ptr or null if the queue was empty + T *dequeuePtr(TickType_t maxWait = portMAX_DELAY) + { + T *p; + return dequeue(&p, maxWait) ? p : nullptr; + } + + void setReader(concurrency::OSThread *t) { reader = t; } + + // For compatibility with PointerQueue interface + int getMaxLen() const { return MaxElements; } +}; diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index 08c03dc6b..224f45de2 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -18,6 +18,21 @@ #define MAX_RX_TOPHONE 32 #endif +/// max number of QueueStatus packets which can be waiting for delivery to phone +#ifndef MAX_RX_QUEUESTATUS_TOPHONE +#define MAX_RX_QUEUESTATUS_TOPHONE 4 +#endif + +/// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone +#ifndef MAX_RX_MQTTPROXY_TOPHONE +#define MAX_RX_MQTTPROXY_TOPHONE 32 +#endif + +/// max number of ClientNotification packets which can be waiting for delivery to phone +#ifndef MAX_RX_NOTIFICATION_TOPHONE +#define MAX_RX_NOTIFICATION_TOPHONE 4 +#endif + /// Verify baseline assumption of node size. If it increases, we need to reevaluate /// the impact of its memory footprint, notably on MAX_NUM_NODES. static_assert(sizeof(meshtastic_NodeInfoLite) <= 200, "NodeInfoLite size increased. Reconsider impact on MAX_NUM_NODES."); diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index f2c34a7c8..6d8bbda68 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -6,9 +6,12 @@ #include "input/RotaryEncoderImpl.h" #include "input/RotaryEncoderInterruptImpl1.h" #include "input/SerialKeyboardImpl.h" -#include "input/TrackballInterruptImpl1.h" #include "input/UpDownInterruptImpl1.h" #include "modules/SystemCommandsModule.h" +#if HAS_TRACKBALL +#include "input/TrackballInterruptImpl1.h" +#endif + #if !MESHTASTIC_EXCLUDE_I2C #include "input/cardKbI2cImpl.h" #endif @@ -135,13 +138,20 @@ void setupModules() traceRouteModule = new TraceRouteModule(); #endif #if !MESHTASTIC_EXCLUDE_NEIGHBORINFO - neighborInfoModule = new NeighborInfoModule(); + if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) { + neighborInfoModule = new NeighborInfoModule(); + } #endif #if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR - detectionSensorModule = new DetectionSensorModule(); + if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) { + detectionSensorModule = new DetectionSensorModule(); + } #endif #if !MESHTASTIC_EXCLUDE_ATAK - atakPluginModule = new AtakPluginModule(); + if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK, + meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) { + atakPluginModule = new AtakPluginModule(); + } #endif #if !MESHTASTIC_EXCLUDE_PKI keyVerificationModule = new KeyVerificationModule(); @@ -207,7 +217,7 @@ void setupModules() aLinuxInputImpl->init(); } #endif -#if !MESHTASTIC_EXCLUDE_INPUTBROKER +#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); @@ -227,11 +237,14 @@ void setupModules() #if HAS_TELEMETRY new DeviceTelemetryModule(); #endif -// TODO: How to improve this? #if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - 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 (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) { + if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled && + nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) { new AirQualityTelemetryModule(); } #endif @@ -243,12 +256,16 @@ void setupModules() #endif #endif #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - 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 (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { + if (moduleConfig.has_serial && moduleConfig.serial.enabled && + config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { new SerialModule(); } #endif @@ -259,19 +276,26 @@ void setupModules() audioModule = new AudioModule(); #endif #if !MESHTASTIC_EXCLUDE_PAXCOUNTER - 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 - storeForwardModule = new StoreForwardModule(); + if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) { + storeForwardModule = new StoreForwardModule(); + } #endif #endif #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION - externalNotificationModule = new ExternalNotificationModule(); + if (moduleConfig.has_external_notification && moduleConfig.external_notification.enabled) { + externalNotificationModule = new ExternalNotificationModule(); + } #endif #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS - new RangeTestModule(); + if (moduleConfig.has_range_test && moduleConfig.range_test.enabled) + new RangeTestModule(); #endif } else { #if !MESHTASTIC_EXCLUDE_ADMIN diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 0060e99fa..276a11b3a 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -12,12 +12,12 @@ NodeInfoModule *nodeInfoModule; bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *pptr) { - auto p = *pptr; - if (mp.from == nodeDB->getNodeNum()) { LOG_WARN("Ignoring packet supposed to be from our own node: %08x", mp.from); return false; } + + auto p = *pptr; if (p.is_licensed != owner.is_licensed) { LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!"); return true; @@ -44,7 +44,10 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal) service->cancelSending(prevPacketId); shorterTimeout = _shorterTimeout; + DEBUG_HEAP_BEFORE; meshtastic_MeshPacket *p = allocReply(); + DEBUG_HEAP_AFTER("NodeInfoModule::sendOurNodeInfo", p); + if (p) { // Check whether we didn't ignore it p->to = dest; p->decoded.want_response = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 08fd09db0..98d5b19d0 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -172,7 +172,10 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage, telemetry.variant.device_metrics.uptime_seconds); + DEBUG_HEAP_BEFORE; meshtastic_MeshPacket *p = allocDataProtobuf(telemetry); + DEBUG_HEAP_AFTER("DeviceTelemetryModule::sendTelemetry", p); + p->to = dest; p->decoded.want_response = false; p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; diff --git a/test/test_meshpacket_serializer/ports/test_text_message.cpp b/test/test_meshpacket_serializer/ports/test_text_message.cpp index de3f34541..0f3b0bc6d 100644 --- a/test/test_meshpacket_serializer/ports/test_text_message.cpp +++ b/test/test_meshpacket_serializer/ports/test_text_message.cpp @@ -1,42 +1,105 @@ #include "../test_helpers.h" +#include + +// Helper function to test common packet fields and structure +void verify_text_message_packet_structure(const std::string &json, const char *expected_text) +{ + TEST_ASSERT_TRUE(json.length() > 0); + + // Use smart pointer for automatic memory management + std::unique_ptr root(JSON::Parse(json.c_str())); + TEST_ASSERT_NOT_NULL(root.get()); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Check basic packet fields - use helper function to reduce duplication + auto check_field = [&](const char *field, uint32_t expected_value) { + auto it = jsonObj.find(field); + TEST_ASSERT_TRUE(it != jsonObj.end()); + TEST_ASSERT_EQUAL(expected_value, (uint32_t)it->second->AsNumber()); + }; + + check_field("from", 0x11223344); + check_field("to", 0x55667788); + check_field("id", 0x9999); + + // Check message type + auto type_it = jsonObj.find("type"); + TEST_ASSERT_TRUE(type_it != jsonObj.end()); + TEST_ASSERT_EQUAL_STRING("text", type_it->second->AsString().c_str()); + + // Check payload + auto payload_it = jsonObj.find("payload"); + TEST_ASSERT_TRUE(payload_it != jsonObj.end()); + TEST_ASSERT_TRUE(payload_it->second->IsObject()); + + JSONObject payload = payload_it->second->AsObject(); + auto text_it = payload.find("text"); + TEST_ASSERT_TRUE(text_it != payload.end()); + TEST_ASSERT_EQUAL_STRING(expected_text, text_it->second->AsString().c_str()); + + // No need for manual delete with smart pointer +} // Test TEXT_MESSAGE_APP port void test_text_message_serialization() { const char *test_text = "Hello Meshtastic!"; meshtastic_MeshPacket packet = - create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, (const uint8_t *)test_text, strlen(test_text)); + create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast(test_text), strlen(test_text)); std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); - TEST_ASSERT_TRUE(json.length() > 0); - - JSONValue *root = JSON::Parse(json.c_str()); - TEST_ASSERT_NOT_NULL(root); - TEST_ASSERT_TRUE(root->IsObject()); - - JSONObject jsonObj = root->AsObject(); - - // Check basic packet fields - TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end()); - TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber()); - - TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end()); - TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber()); - - TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end()); - TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber()); - - // Check message type - TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end()); - TEST_ASSERT_EQUAL_STRING("text", jsonObj["type"]->AsString().c_str()); - - // Check payload - TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); - TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); - - JSONObject payload = jsonObj["payload"]->AsObject(); - TEST_ASSERT_TRUE(payload.find("text") != payload.end()); - TEST_ASSERT_EQUAL_STRING("Hello Meshtastic!", payload["text"]->AsString().c_str()); - - delete root; + verify_text_message_packet_structure(json, test_text); } + +// Test with nullptr to check robustness +void test_text_message_serialization_null() +{ + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + verify_text_message_packet_structure(json, ""); +} + +// Test TEXT_MESSAGE_APP port with very long message (boundary testing) +void test_text_message_serialization_long_text() +{ + // Test with actual message size limits + constexpr size_t MAX_MESSAGE_SIZE = 200; // Typical LoRa payload limit + std::string long_text(MAX_MESSAGE_SIZE, 'A'); + + meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, + reinterpret_cast(long_text.c_str()), long_text.length()); + + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + verify_text_message_packet_structure(json, long_text.c_str()); +} + +// Test with message over size limit (should fail) +void test_text_message_serialization_oversized() +{ + constexpr size_t OVERSIZED_MESSAGE = 250; // Over the limit + std::string oversized_text(OVERSIZED_MESSAGE, 'B'); + + meshtastic_MeshPacket packet = create_test_packet( + meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast(oversized_text.c_str()), oversized_text.length()); + + // Should fail or return empty/error + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + // Should only verify first 234 characters for oversized messages + std::string expected_text = oversized_text.substr(0, 234); + verify_text_message_packet_structure(json, expected_text.c_str()); +} + +// Add test for malformed UTF-8 sequences +void test_text_message_serialization_invalid_utf8() +{ + const uint8_t invalid_utf8[] = {0xFF, 0xFE, 0xFD, 0x00}; // Invalid UTF-8 + meshtastic_MeshPacket packet = + create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, invalid_utf8, sizeof(invalid_utf8) - 1); + + // Should not crash, may produce replacement characters + std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); + TEST_ASSERT_TRUE(json.length() > 0); +} \ No newline at end of file diff --git a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini index 8b86e0217..de4714efa 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini +++ b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini @@ -70,6 +70,7 @@ build_flags = ${ft5x06.build_flags} -D LGFX_SCREEN_WIDTH=240 -D LGFX_SCREEN_HEIGHT=320 + -D DISPLAY_SIZE=320x240 ; landscape mode -D LGFX_PANEL=ST7789 -D LGFX_ROTATION=1 -D LGFX_TOUCH_X_MIN=0 diff --git a/variants/native/portduino-buildroot/variant.h b/variants/native/portduino-buildroot/variant.h index b7b39d6e8..11a6c0bd3 100644 --- a/variants/native/portduino-buildroot/variant.h +++ b/variants/native/portduino-buildroot/variant.h @@ -2,4 +2,4 @@ #define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE settingsMap[maxtophone] -#define MAX_NUM_NODES settingsMap[maxnodes] \ No newline at end of file +#define MAX_NUM_NODES settingsMap[maxnodes]