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 81f95a7e3..e2e5e1a18 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 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/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/MeshService.cpp b/src/mesh/MeshService.cpp index 7e35fccd8..607766ab6 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -193,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) { @@ -250,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/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index d11eff9e7..c6c3415bb 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -115,6 +115,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 221e0275b..27879ce54 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); diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 85d183aef..d4beb6824 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,10 +138,14 @@ 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 if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK, @@ -210,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); @@ -230,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 @@ -246,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 @@ -262,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..86ceaae24 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -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;