From 833f950edc1df99609135e6469f3d73c483c5a39 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 16 Oct 2025 09:36:54 -0500 Subject: [PATCH 1/6] Move loadProto and saveProto into FSCommon --- src/FSCommon.cpp | 62 +++++++++++++++++++ src/FSCommon.h | 19 +++++- src/graphics/draw/MenuHandler.cpp | 3 +- .../niche/Utils/CannedMessageStore.cpp | 10 +-- src/mesh/NodeDB.cpp | 62 ------------------- src/mesh/NodeDB.h | 18 ------ src/modules/AdminModule.cpp | 2 +- src/modules/CannedMessageModule.cpp | 10 +-- src/modules/ExternalNotificationModule.cpp | 7 ++- 9 files changed, 97 insertions(+), 96 deletions(-) diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index f215be80f..a57233e9d 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -10,7 +10,10 @@ */ #include "FSCommon.h" #include "SPILock.h" +#include "SafeFile.h" #include "configuration.h" +#include +#include // Software SPI is used by MUI so disable SD card here until it's also implemented #if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) @@ -335,4 +338,63 @@ void setupSDCard() LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024))); LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024))); #endif +} + +/** Load a protobuf from a file, return LoadFileResult */ +LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct) +{ + LoadFileResult state = LoadFileResult::OTHER_FAILURE; +#ifdef FSCom + concurrency::LockGuard g(spiLock); + + auto f = FSCom.open(filename, FILE_O_READ); + + if (f) { + LOG_INFO("Load %s", filename); + pb_istream_t stream = {&readcb, &f, protoSize}; + if (fields != &meshtastic_NodeDatabase_msg) // contains a vector object + memset(dest_struct, 0, objSize); + if (!pb_decode(&stream, fields, dest_struct)) { + LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream)); + state = LoadFileResult::DECODE_FAILED; + } else { + LOG_INFO("Loaded %s successfully", filename); + state = LoadFileResult::LOAD_SUCCESS; + } + f.close(); + } else { + LOG_ERROR("Could not open / read %s", filename); + } +#else + LOG_ERROR("ERROR: Filesystem not implemented"); + state = LoadFileResult::NO_FILESYSTEM; +#endif + return state; +} + +/** Save a protobuf from a file, return true for success */ +bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, bool fullAtomic) +{ + bool okay = false; +#ifdef FSCom + auto f = SafeFile(filename, fullAtomic); + + LOG_INFO("Save %s", filename); + pb_ostream_t stream = {&writecb, static_cast(&f), protoSize}; + + if (!pb_encode(&stream, fields, dest_struct)) { + LOG_ERROR("Error: can't encode protobuf %s", PB_GET_ERROR(&stream)); + } else { + okay = true; + } + + bool writeSucceeded = f.close(); + + if (!okay || !writeSucceeded) { + LOG_ERROR("Can't write prefs!"); + } +#else + LOG_ERROR("ERROR: Filesystem not implemented"); +#endif + return okay; } \ No newline at end of file diff --git a/src/FSCommon.h b/src/FSCommon.h index fdc0b76ec..eb4ada8e7 100644 --- a/src/FSCommon.h +++ b/src/FSCommon.h @@ -3,6 +3,19 @@ #include "configuration.h" #include +enum LoadFileResult { + // Successfully opened the file + LOAD_SUCCESS = 1, + // File does not exist + NOT_FOUND = 2, + // Device does not have a filesystem + NO_FILESYSTEM = 3, + // File exists, but could not decode protobufs + DECODE_FAILED = 4, + // File exists, but open failed for some reason + OTHER_FAILURE = 5 +}; + // Cross platform filesystem API #if defined(ARCH_PORTDUINO) @@ -55,4 +68,8 @@ bool renameFile(const char *pathFrom, const char *pathTo); std::vector getFiles(const char *dirname, uint8_t levels); void listDir(const char *dirname, uint8_t levels, bool del = false); void rmDir(const char *dirname); -void setupSDCard(); \ No newline at end of file +void setupSDCard(); +LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct); + +bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, + bool fullAtomic = true); \ No newline at end of file diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 701062e08..4677b0891 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -1,6 +1,7 @@ #include "configuration.h" #if HAS_SCREEN #include "ClockRenderer.h" +#include "FSCommon.h" #include "GPS.h" #include "MenuHandler.h" #include "MeshRadio.h" @@ -1700,7 +1701,7 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display) void menuHandler::saveUIConfig() { - nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); + saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig); } } // namespace graphics diff --git a/src/graphics/niche/Utils/CannedMessageStore.cpp b/src/graphics/niche/Utils/CannedMessageStore.cpp index 50998930d..cf1533c62 100644 --- a/src/graphics/niche/Utils/CannedMessageStore.cpp +++ b/src/graphics/niche/Utils/CannedMessageStore.cpp @@ -53,9 +53,9 @@ void CannedMessageStore::load() // Attempt to load the bulk canned message data from flash meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig; - LoadFileResult result = nodeDB->loadProto("/prefs/cannedConf.proto", meshtastic_CannedMessageModuleConfig_size, - sizeof(meshtastic_CannedMessageModuleConfig), - &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); + LoadFileResult result = loadProto("/prefs/cannedConf.proto", meshtastic_CannedMessageModuleConfig_size, + sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg, + &cannedMessageModuleConfig); // Abort if nothing to load if (result != LoadFileResult::LOAD_SUCCESS || strlen(cannedMessageModuleConfig.messages) == 0) @@ -129,8 +129,8 @@ void CannedMessageStore::handleSet(const meshtastic_AdminMessage *request) #endif // Write to flash - nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, - &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); + saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, &meshtastic_CannedMessageModuleConfig_msg, + &cannedMessageModuleConfig); // Reload from flash, to update the canned messages in RAM // (This is a lazy way to handle it) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index dec8411fe..a743439a9 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -15,7 +15,6 @@ #include "RTC.h" #include "Router.h" #include "SPILock.h" -#include "SafeFile.h" #include "TypeConversions.h" #include "error.h" #include "main.h" @@ -1105,39 +1104,6 @@ void NodeDB::pickNewNodeNum() myNodeInfo.my_node_num = nodeNum; } -/** Load a protobuf from a file, return LoadFileResult */ -LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, - void *dest_struct) -{ - LoadFileResult state = LoadFileResult::OTHER_FAILURE; -#ifdef FSCom - concurrency::LockGuard g(spiLock); - - auto f = FSCom.open(filename, FILE_O_READ); - - if (f) { - LOG_INFO("Load %s", filename); - pb_istream_t stream = {&readcb, &f, protoSize}; - if (fields != &meshtastic_NodeDatabase_msg) // contains a vector object - memset(dest_struct, 0, objSize); - if (!pb_decode(&stream, fields, dest_struct)) { - LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream)); - state = LoadFileResult::DECODE_FAILED; - } else { - LOG_INFO("Loaded %s successfully", filename); - state = LoadFileResult::LOAD_SUCCESS; - } - f.close(); - } else { - LOG_ERROR("Could not open / read %s", filename); - } -#else - LOG_ERROR("ERROR: Filesystem not implemented"); - state = LoadFileResult::NO_FILESYSTEM; -#endif - return state; -} - void NodeDB::loadFromDisk() { // Mark the current device state as completely unusable, so that if we fail reading the entire file from @@ -1351,34 +1317,6 @@ void NodeDB::loadFromDisk() #endif } -/** Save a protobuf from a file, return true for success */ -bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, - bool fullAtomic) -{ - bool okay = false; -#ifdef FSCom - auto f = SafeFile(filename, fullAtomic); - - LOG_INFO("Save %s", filename); - pb_ostream_t stream = {&writecb, static_cast(&f), protoSize}; - - if (!pb_encode(&stream, fields, dest_struct)) { - LOG_ERROR("Error: can't encode protobuf %s", PB_GET_ERROR(&stream)); - } else { - okay = true; - } - - bool writeSucceeded = f.close(); - - if (!okay || !writeSucceeded) { - LOG_ERROR("Can't write prefs!"); - } -#else - LOG_ERROR("ERROR: Filesystem not implemented"); -#endif - return okay; -} - bool NodeDB::saveChannelsToDisk() { #ifdef FSCom diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index e8724f2c9..42e7c2f75 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -110,19 +110,6 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n); /// Given a packet, return how many seconds in the past (vs now) it was received uint32_t sinceReceived(const meshtastic_MeshPacket *p); -enum LoadFileResult { - // Successfully opened the file - LOAD_SUCCESS = 1, - // File does not exist - NOT_FOUND = 2, - // Device does not have a filesystem - NO_FILESYSTEM = 3, - // File exists, but could not decode protobufs - DECODE_FAILED = 4, - // File exists, but open failed for some reason - OTHER_FAILURE = 5 -}; - enum UserLicenseStatus { NotKnown, NotLicensed, Licensed }; class NodeDB @@ -233,11 +220,6 @@ class NodeDB bool factoryReset(bool eraseBleBonds = false); - LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, - void *dest_struct); - bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, - bool fullAtomic = true); - void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role); const meshtastic_NodeInfoLite *readNextMeshNode(uint32_t &readIndex); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index d300ff53b..adf74fef5 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -1306,7 +1306,7 @@ void AdminModule::saveChanges(int saveWhat, bool shouldReboot) void AdminModule::handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg) { - nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uicfg); + saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uicfg); } void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index e9f52bb7d..fad8bd54c 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -2185,9 +2185,9 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket & void CannedMessageModule::loadProtoForModule() { - if (nodeDB->loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, - sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg, - &cannedMessageModuleConfig) != LoadFileResult::LOAD_SUCCESS) { + if (loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, + sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg, + &cannedMessageModuleConfig) != LoadFileResult::LOAD_SUCCESS) { installDefaultCannedMessageModuleConfig(); } } @@ -2207,8 +2207,8 @@ bool CannedMessageModule::saveProtoForModule() spiLock->unlock(); #endif - okay &= nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, - &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); + okay &= saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, + &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); return okay; } diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index ffc789275..a97a137eb 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -14,6 +14,7 @@ * @date [Insert Date] */ #include "ExternalNotificationModule.h" +#include "FSCommon.h" #include "MeshService.h" #include "NodeDB.h" #include "RTC.h" @@ -370,8 +371,8 @@ ExternalNotificationModule::ExternalNotificationModule() if (inputBroker) // put our callback in the inputObserver list inputObserver.observe(inputBroker); #endif - if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), - &meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) { + if (loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), &meshtastic_RTTTLConfig_msg, + &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) { memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone)); // The default ringtone is always loaded from userPrefs.jsonc strncpy(rtttlConfig.ringtone, USERPREFS_RINGTONE_RTTTL, sizeof(rtttlConfig.ringtone)); @@ -627,7 +628,7 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg) } if (changed) { - nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); + saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); } } From 770085ddf7e88c775c78fdaa0806e99db8ea3cb6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 16 Oct 2025 09:44:35 -0500 Subject: [PATCH 2/6] Begin refactoring nodedb.cpp with public wrappers --- src/mesh/NodeDB.cpp | 40 ++++++++++++++++++++-------------------- src/mesh/NodeDB.h | 35 ++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index a743439a9..21c15715d 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -313,7 +313,7 @@ NodeDB::NodeDB() LOG_DEBUG("Number of Device Reboots: %d", myNodeInfo.reboot_count); #endif - resetRadioConfig(); // If bogus settings got saved, then fix them + _resetRadioConfig(); // If bogus settings got saved, then fix them // nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d", config.lora.region, myNodeInfo.my_node_num, numMeshNodes); // Uncomment below to always enable UDP broadcasts @@ -430,7 +430,7 @@ NodeDB::NodeDB() } #endif sortMeshDB(); - saveToDisk(saveWhat); + _saveToDisk(saveWhat); } /** @@ -459,7 +459,7 @@ bool isBroadcast(uint32_t dest) return dest == NODENUM_BROADCAST || dest == NODENUM_BROADCAST_NO_LORA; } -void NodeDB::resetRadioConfig(bool is_fresh_install) +void NodeDB::_resetRadioConfig(bool is_fresh_install) { if (is_fresh_install) { radioGeneration++; @@ -497,7 +497,7 @@ bool NodeDB::factoryReset(bool eraseBleBonds) installDefaultModuleConfig(); installDefaultChannels(); // third, write everything to disk - saveToDisk(); + _saveToDisk(); if (eraseBleBonds) { LOG_INFO("Erase BLE bonds"); #ifdef ARCH_ESP32 @@ -648,7 +648,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.device.node_info_broadcast_secs = default_node_info_broadcast_secs; config.security.serial_enabled = true; config.security.admin_channel_enabled = false; - resetRadioConfig(true); // This also triggers NodeInfo/Position requests since we're fresh + _resetRadioConfig(true); // This also triggers NodeInfo/Position requests since we're fresh strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32); #if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \ @@ -735,7 +735,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) #ifdef USERPREFS_CONFIG_DEVICE_ROLE // Apply role-specific defaults when role is set via user preferences - installRoleDefaults(config.device.role); + _installRoleDefaults(config.device.role); #endif initConfigIntervals(); @@ -893,7 +893,7 @@ void NodeDB::installDefaultModuleConfig() initModuleConfigIntervals(); } -void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) +void NodeDB::_installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) { if (role == meshtastic_Config_DeviceConfig_Role_ROUTER) { initConfigIntervals(); @@ -1203,7 +1203,7 @@ void NodeDB::loadFromDisk() if (backupSecurity.private_key.size > 0) { LOG_DEBUG("Restoring backup of security config"); config.security = backupSecurity; - saveToDisk(SEGMENT_CONFIG); + _saveToDisk(SEGMENT_CONFIG); } // Make sure we load hard coded admin keys even when the configuration file has none. @@ -1254,7 +1254,7 @@ void NodeDB::loadFromDisk() if (numAdminKeys > 0) { LOG_INFO("Saving %d hard coded admin keys.", numAdminKeys); config.security.admin_key_count = numAdminKeys; - saveToDisk(SEGMENT_CONFIG); + _saveToDisk(SEGMENT_CONFIG); } state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig), @@ -1306,7 +1306,7 @@ void NodeDB::loadFromDisk() if (moduleConfig.paxcounter.paxcounter_update_interval == 900) moduleConfig.paxcounter.paxcounter_update_interval = 0; - saveToDisk(SEGMENT_MODULECONFIG); + _saveToDisk(SEGMENT_MODULECONFIG); } #if ARCH_PORTDUINO // set any config overrides @@ -1405,7 +1405,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) return success; } -bool NodeDB::saveToDisk(int saveWhat) +bool NodeDB::_saveToDisk(int saveWhat) { LOG_DEBUG("Save to disk %d", saveWhat); bool success = saveToDiskNoRetry(saveWhat); @@ -1460,7 +1460,7 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p) #define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline -size_t NodeDB::getNumOnlineMeshNodes(bool localOnly) +size_t NodeDB::_getNumOnlineMeshNodes(bool localOnly) { size_t numseen = 0; @@ -1599,7 +1599,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde } #if !(MESHTASTIC_EXCLUDE_PKI) - if (p.public_key.size == 32 && nodeId != nodeDB->getNodeNum()) { + if (p.public_key.size == 32 && nodeId != getNodeNum()) { printBytes("Incoming Pubkey: ", p.public_key.bytes, 32); // Alert the user if a remote node is advertising public key that matches our own @@ -1656,7 +1656,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde // store our DB unless we just did so less than a minute ago if (!Throttle::isWithinTimespanMs(lastNodeDbSave, ONE_MINUTE_MS)) { - saveToDisk(SEGMENT_NODEDATABASE); + _saveToDisk(SEGMENT_NODEDATABASE); lastNodeDbSave = millis(); } else { LOG_DEBUG("Defer NodeDB saveToDisk for now"); @@ -1733,25 +1733,25 @@ bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p) // 1. doing only one pass through the database, instead of two // 2. exiting early when a favorite is found, or if both from and to have been seen - if (p.to == NODENUM_BROADCAST) - return isFavorite(p.from); // we never store NODENUM_BROADCAST in the DB, so we only need to check p.from - meshtastic_NodeInfoLite *lite = NULL; bool seenFrom = false; bool seenTo = false; + if (p.to == NODENUM_BROADCAST) + seenTo = true; + for (int i = 0; i < numMeshNodes; i++) { lite = &meshNodes->at(i); - if (lite->num == p.from) { + if (!seenFrom && lite->num == p.from) { if (lite->is_favorite) return true; seenFrom = true; } - if (lite->num == p.to) { + if (!seenTo && lite->num == p.to) { if (lite->is_favorite) return true; @@ -1995,7 +1995,7 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, LOG_DEBUG("Restored channels"); } - success = saveToDisk(restoreWhat); + success = _saveToDisk(restoreWhat); if (success) { LOG_INFO("Restored preferences from backup"); } else { diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 42e7c2f75..f2d100309 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -122,7 +122,6 @@ class NodeDB // Note: these two references just point into our static array we serialize to/from disk public: - std::vector *meshNodes; bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI Observable newStatus; @@ -138,17 +137,18 @@ class NodeDB /// write to flash /// @return true if the save was successful bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS | - SEGMENT_NODEDATABASE); + SEGMENT_NODEDATABASE) + { + return _saveToDisk(saveWhat); + } /** Reinit radio config if needed, because either: * a) sometimes a buggy android app might send us bogus settings or * b) the client set factory_reset * - * @param factory_reset if true, reset all settings to factory defaults * @param is_fresh_install set to true after a fresh install, to trigger NodeInfo/Position requests - * @return true if the config was completely reset, in that case, we should send it back to the client */ - void resetRadioConfig(bool is_fresh_install = false); + void resetRadioConfig(bool is_fresh_install = false) { _resetRadioConfig(is_fresh_install); } /// given a subpacket sniffed from the network, update our DB state /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw @@ -214,13 +214,13 @@ class NodeDB /* Return the number of nodes we've heard from recently (within the last 2 hrs?) * @param localOnly if true, ignore nodes heard via MQTT */ - size_t getNumOnlineMeshNodes(bool localOnly = false); + size_t getNumOnlineMeshNodes(bool localOnly = false) { return _getNumOnlineMeshNodes(localOnly); } - void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum); + void resetNodes(), removeNodeByNum(NodeNum nodeNum); bool factoryReset(bool eraseBleBonds = false); - void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role); + void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) { _installRoleDefaults(role); } const meshtastic_NodeInfoLite *readNextMeshNode(uint32_t &readIndex); @@ -274,11 +274,13 @@ class NodeDB void notifyObservers(bool forceUpdate = false) { // Notify observers of the current node state - const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate); + const meshtastic::NodeStatus status = meshtastic::NodeStatus(_getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate); newStatus.notifyObservers(&status); } private: + std::vector *meshNodes; + bool duplicateWarned = false; uint32_t lastNodeDbSave = 0; // when we last saved our db to flash uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually @@ -312,6 +314,21 @@ class NodeDB bool saveDeviceStateToDisk(); bool saveNodeDatabaseToDisk(); void sortMeshDB(); + + void initConfigIntervals(), initModuleConfigIntervals(); + + // wrapped private functions: + + bool _saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS | + SEGMENT_NODEDATABASE); + void _resetRadioConfig(bool is_fresh_install = false); + + /* Return the number of nodes we've heard from recently (within the last 2 hrs?) + * @param localOnly if true, ignore nodes heard via MQTT + */ + size_t _getNumOnlineMeshNodes(bool localOnly = false); + + void _installRoleDefaults(meshtastic_Config_DeviceConfig_Role role); }; extern NodeDB *nodeDB; From 10141b2562a6620da32b98d82b998e741acdc466 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 16 Oct 2025 12:08:00 -0500 Subject: [PATCH 3/6] InkHUD: don't try to access meshNodes directly --- .../InkHUD/Applets/User/Heard/HeardApplet.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp index 5a659c606..aeaa78cda 100644 --- a/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp @@ -62,22 +62,12 @@ void InkHUD::HeardApplet::populateFromNodeDB() { // Fill a collection with pointers to each node in db std::vector ordered; - for (auto mn = nodeDB->meshNodes->begin(); mn != nodeDB->meshNodes->end(); ++mn) { - // Only copy if valid, and not our own node + for (int i = 1; i < maxCards(); i++) { + auto mn = nodeDB->getMeshNodeByIndex(i); if (mn->num != 0 && mn->num != nodeDB->getNodeNum()) ordered.push_back(&*mn); } - // Sort the collection by age - std::sort(ordered.begin(), ordered.end(), [](meshtastic_NodeInfoLite *top, meshtastic_NodeInfoLite *bottom) -> bool { - return (top->last_heard > bottom->last_heard); - }); - - // Keep the most recent entries only - // Just enough to fill the screen - if (ordered.size() > maxCards()) - ordered.resize(maxCards()); - // Create card info for these (stale) node observations meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); for (meshtastic_NodeInfoLite *node : ordered) { From dc6e1093293d08ab74d050719f13cc9c31279603 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 16 Oct 2025 14:37:33 -0500 Subject: [PATCH 4/6] Finish adding NodeDB wrapper classes --- src/mesh/NodeDB.cpp | 28 +++++++++---------- src/mesh/NodeDB.h | 66 ++++++++++++++++++++++++++------------------- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 21c15715d..d2dcad802 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1007,9 +1007,9 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum) saveNodeDatabaseToDisk(); } -void NodeDB::clearLocalPosition() +void NodeDB::_clearLocalPosition() { - meshtastic_NodeInfoLite *node = getMeshNode(nodeDB->getNodeNum()); + meshtastic_NodeInfoLite *node = _getMeshNode(nodeDB->getNodeNum()); node->position.latitude_i = 0; node->position.longitude_i = 0; node->position.altitude = 0; @@ -1090,7 +1090,7 @@ void NodeDB::pickNewNodeNum() } meshtastic_NodeInfoLite *found; - while (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) || + while (((found = _getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) || (nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) { NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice if (found) @@ -1519,7 +1519,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou } info->has_position = true; updateGUIforNode = info; - notifyObservers(true); // Force an update whether or not our node counts have changed + _notifyObservers(true); // Force an update whether or not our node counts have changed } /** Update telemetry info for this node based on received metrics @@ -1542,7 +1542,7 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS info->device_metrics = t.variant.device_metrics; info->has_device_metrics = true; updateGUIforNode = info; - notifyObservers(true); // Force an update whether or not our node counts have changed + _notifyObservers(true); // Force an update whether or not our node counts have changed } /** @@ -1584,7 +1584,7 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact) // Mark the node's key as manually verified to indicate trustworthiness. updateGUIforNode = info; sortMeshDB(); - notifyObservers(true); // Force an update whether or not our node counts have changed + _notifyObservers(true); // Force an update whether or not our node counts have changed } saveNodeDatabaseToDisk(); } @@ -1650,7 +1650,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde if (changed) { updateGUIforNode = info; - notifyObservers(true); // Force an update whether or not our node counts have changed + _notifyObservers(true); // Force an update whether or not our node counts have changed // We just changed something about a User, // store our DB unless we just did so less than a minute ago @@ -1701,7 +1701,7 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId) { - meshtastic_NodeInfoLite *lite = getMeshNode(nodeId); + meshtastic_NodeInfoLite *lite = _getMeshNode(nodeId); if (lite && lite->is_favorite != is_favorite) { lite->is_favorite = is_favorite; sortMeshDB(); @@ -1717,7 +1717,7 @@ bool NodeDB::isFavorite(uint32_t nodeId) if (nodeId == NODENUM_BROADCAST) return false; - meshtastic_NodeInfoLite *lite = getMeshNode(nodeId); + meshtastic_NodeInfoLite *lite = _getMeshNode(nodeId); if (lite) { return lite->is_favorite; @@ -1805,7 +1805,7 @@ void NodeDB::sortMeshDB() uint8_t NodeDB::getMeshNodeChannel(NodeNum n) { - const meshtastic_NodeInfoLite *info = getMeshNode(n); + const meshtastic_NodeInfoLite *info = _getMeshNode(n); if (!info) { return 0; // defaults to PRIMARY } @@ -1821,7 +1821,7 @@ std::string NodeDB::getNodeId() const /// Find a node in our DB, return null for missing /// NOTE: This function might be called from an ISR -meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n) +meshtastic_NodeInfoLite *NodeDB::_getMeshNode(NodeNum n) { for (int i = 0; i < numMeshNodes; i++) if (meshNodes->at(i).num == n) @@ -1831,7 +1831,7 @@ meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n) } // returns true if the maximum number of nodes is reached or we are running low on memory -bool NodeDB::isFull() +bool NodeDB::_isFull() { return (numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < MINIMUM_SAFE_FREE_HEAP); } @@ -1839,7 +1839,7 @@ bool NodeDB::isFull() /// Find a node in our DB, create an empty NodeInfo if missing meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) { - meshtastic_NodeInfoLite *lite = getMeshNode(n); + meshtastic_NodeInfoLite *lite = _getMeshNode(n); if (!lite) { if (isFull()) { @@ -1901,7 +1901,7 @@ bool NodeDB::hasValidPosition(const meshtastic_NodeInfoLite *n) /// we consider them licensed UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum) { - meshtastic_NodeInfoLite *info = getMeshNode(nodeNum); + meshtastic_NodeInfoLite *info = _getMeshNode(nodeNum); if (!info || !info->has_user) { return UserLicenseStatus::NotKnown; } diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index f2d100309..229f6b14e 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -230,55 +230,35 @@ class NodeDB return &meshNodes->at(x); } - virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n); + meshtastic_NodeInfoLite *getMeshNode(NodeNum n) { return getMeshNode(n); } size_t getNumMeshNodes() { return numMeshNodes; } UserLicenseStatus getLicenseStatus(uint32_t nodeNum); - size_t getMaxNodesAllocatedSize() - { - meshtastic_NodeDatabase emptyNodeDatabase; - emptyNodeDatabase.version = DEVICESTATE_CUR_VER; - size_t nodeDatabaseSize; - pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase); - return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeInfoLite_size); - } - // returns true if the maximum number of nodes is reached or we are running low on memory - bool isFull(); + bool isFull() { return _isFull(); } - void clearLocalPosition(); + void clearLocalPosition() { _clearLocalPosition(); } - void setLocalPosition(meshtastic_Position position, bool timeOnly = false) - { - if (timeOnly) { - LOG_DEBUG("Set local position time only: time=%u timestamp=%u", position.time, position.timestamp); - localPosition.time = position.time; - localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time; - return; - } - LOG_DEBUG("Set local position: lat=%i lon=%i time=%u timestamp=%u", position.latitude_i, position.longitude_i, - position.time, position.timestamp); - localPosition = position; - } + void setLocalPosition(meshtastic_Position position, bool timeOnly = false) { _setLocalPosition(position, timeOnly); } bool hasValidPosition(const meshtastic_NodeInfoLite *n); - bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest); - bool backupPreferences(meshtastic_AdminMessage_BackupLocation location); bool restorePreferences(meshtastic_AdminMessage_BackupLocation location, int restoreWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS); + void notifyObservers(bool forceUpdate = false) { _notifyObservers(forceUpdate); } + + private: /// Notify observers of changes to the DB - void notifyObservers(bool forceUpdate = false) + void _notifyObservers(bool forceUpdate = false) { // Notify observers of the current node state const meshtastic::NodeStatus status = meshtastic::NodeStatus(_getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate); newStatus.notifyObservers(&status); } - private: std::vector *meshNodes; bool duplicateWarned = false; @@ -317,6 +297,17 @@ class NodeDB void initConfigIntervals(), initModuleConfigIntervals(); + size_t getMaxNodesAllocatedSize() + { + meshtastic_NodeDatabase emptyNodeDatabase; + emptyNodeDatabase.version = DEVICESTATE_CUR_VER; + size_t nodeDatabaseSize; + pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase); + return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeInfoLite_size); + } + + bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest); + // wrapped private functions: bool _saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS | @@ -329,6 +320,25 @@ class NodeDB size_t _getNumOnlineMeshNodes(bool localOnly = false); void _installRoleDefaults(meshtastic_Config_DeviceConfig_Role role); + + meshtastic_NodeInfoLite *_getMeshNode(NodeNum n); + + bool _isFull(); + + void _clearLocalPosition(); + + void _setLocalPosition(meshtastic_Position position, bool timeOnly = false) + { + if (timeOnly) { + LOG_DEBUG("Set local position time only: time=%u timestamp=%u", position.time, position.timestamp); + localPosition.time = position.time; + localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time; + return; + } + LOG_DEBUG("Set local position: lat=%i lon=%i time=%u timestamp=%u", position.latitude_i, position.longitude_i, + position.time, position.timestamp); + localPosition = position; + } }; extern NodeDB *nodeDB; From 4a3d28f06b46bc4cb51d08d8c38f850f92ca10eb Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 17 Oct 2025 00:36:26 -0500 Subject: [PATCH 5/6] Add FUNCTION_START and FUNCTION_END to public NodeDB functions --- src/mesh/NodeDB.cpp | 124 +++++++++++++++++++++++++++++++------------- src/mesh/NodeDB.h | 100 ++++++++++++++++++++++++++++++----- 2 files changed, 174 insertions(+), 50 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d2dcad802..af71da733 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -424,7 +424,7 @@ NodeDB::NodeDB() config.has_position = true; info->has_position = true; info->position = TypeConversions::ConvertToPositionLite(fixedGPS); - nodeDB->setLocalPosition(fixedGPS); + nodeDB->_setLocalPosition(fixedGPS); config.position.fixed_position = true; #endif } @@ -479,6 +479,7 @@ void NodeDB::_resetRadioConfig(bool is_fresh_install) bool NodeDB::factoryReset(bool eraseBleBonds) { + FUNCTION_START("factoryReset"); LOG_INFO("Perform factory reset!"); // first, remove the "/prefs" (this removes most prefs) spiLock->lock(); @@ -512,6 +513,7 @@ bool NodeDB::factoryReset(bool eraseBleBonds) Bluefruit.Central.clearBonds(); #endif } + FUNCTION_END; return true; } @@ -979,6 +981,7 @@ void NodeDB::installDefaultChannels() void NodeDB::resetNodes() { + FUNCTION_START("resetNodes"); if (!config.position.fixed_position) clearLocalPosition(); numMeshNodes = 1; @@ -989,10 +992,12 @@ void NodeDB::resetNodes() saveDeviceStateToDisk(); if (neighborInfoModule && moduleConfig.neighbor_info.enabled) neighborInfoModule->resetNeighbors(); + FUNCTION_END; } void NodeDB::removeNodeByNum(NodeNum nodeNum) { + FUNCTION_START("removeNodeByNum"); int newPos = 0, removed = 0; for (int i = 0; i < numMeshNodes; i++) { if (meshNodes->at(i).num != nodeNum) @@ -1005,6 +1010,7 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum) meshtastic_NodeInfoLite()); LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed); saveNodeDatabaseToDisk(); + FUNCTION_END; } void NodeDB::_clearLocalPosition() @@ -1014,7 +1020,7 @@ void NodeDB::_clearLocalPosition() node->position.longitude_i = 0; node->position.altitude = 0; node->position.time = 0; - setLocalPosition(meshtastic_Position_init_default); + _setLocalPosition(meshtastic_Position_init_default); } void NodeDB::cleanupMeshDB() @@ -1429,10 +1435,12 @@ bool NodeDB::_saveToDisk(int saveWhat) const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex) { + FUNCTION_START("readNextMeshNode"); + meshtastic_NodeInfoLite *retVal = nullptr; if (readIndex < numMeshNodes) - return &meshNodes->at(readIndex++); - else - return NULL; + retVal = &meshNodes->at(readIndex++); + FUNCTION_END; + return retVal; } /// Given a node, return how many seconds in the past (vs now) that we last heard from it @@ -1482,8 +1490,10 @@ size_t NodeDB::_getNumOnlineMeshNodes(bool localOnly) */ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src) { + FUNCTION_START("updatePosition"); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId); if (!info) { + FUNCTION_END; return; } @@ -1492,7 +1502,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d", p.timestamp, p.time, p.latitude_i, p.longitude_i, p.altitude); - setLocalPosition(p); + _setLocalPosition(p); info->position = TypeConversions::ConvertToPositionLite(p); } else if ((p.time > 0) && !p.latitude_i && !p.longitude_i && !p.timestamp && !p.location_source) { // FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO @@ -1520,6 +1530,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou info->has_position = true; updateGUIforNode = info; _notifyObservers(true); // Force an update whether or not our node counts have changed + FUNCTION_END; } /** Update telemetry info for this node based on received metrics @@ -1527,9 +1538,11 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou */ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src) { + FUNCTION_START("updatePosition"); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId); // Environment metrics should never go to NodeDb but we'll safegaurd anyway if (!info || t.which_variant != meshtastic_Telemetry_device_metrics_tag) { + FUNCTION_END; return; } @@ -1543,6 +1556,7 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS info->has_device_metrics = true; updateGUIforNode = info; _notifyObservers(true); // Force an update whether or not our node counts have changed + FUNCTION_END; } /** @@ -1550,8 +1564,10 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS */ void NodeDB::addFromContact(meshtastic_SharedContact contact) { + FUNCTION_START("addFromContact"); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num); if (!info || !contact.has_user) { + FUNCTION_END; return; } // If the local node has this node marked as manually verified @@ -1560,6 +1576,7 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact) 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) { + FUNCTION_END; return; } } @@ -1587,14 +1604,18 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact) _notifyObservers(true); // Force an update whether or not our node counts have changed } saveNodeDatabaseToDisk(); + FUNCTION_END; } /** Update user info and channel for this node based on received user data */ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex) { + FUNCTION_START("updateUser"); + meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId); if (!info) { + FUNCTION_END; return false; } @@ -1616,6 +1637,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde sprintf(cn->message, warning, p.long_name); service->sendClientNotification(cn); } + FUNCTION_END; return false; } } @@ -1623,6 +1645,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde // if the key doesn't match, don't update nodeDB at all. if (p.public_key.size != 32 || (memcmp(p.public_key.bytes, info->user.public_key.bytes, 32) != 0)) { LOG_WARN("Public Key mismatch, dropping NodeInfo"); + FUNCTION_END; return false; } LOG_INFO("Public Key set for node, not updating!"); @@ -1662,7 +1685,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde LOG_DEBUG("Defer NodeDB saveToDisk for now"); } } - + FUNCTION_END; return changed; } @@ -1670,63 +1693,68 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) { + FUNCTION_START("updateFrom"); if (mp.from == getNodeNum()) { LOG_DEBUG("Ignore update from self"); - return; - } - if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) { + } else if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) { LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getFrom(&mp)); - if (!info) { - return; + if (info) { + if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard + info->last_heard = mp.rx_time; + + if (mp.rx_snr) + info->snr = mp.rx_snr; // keep the most recent SNR we received for this node. + + info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT + + // If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway + if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) { + info->has_hops_away = true; + info->hops_away = mp.hop_start - mp.hop_limit; + } + sortMeshDB(); } - - if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard - info->last_heard = mp.rx_time; - - if (mp.rx_snr) - info->snr = mp.rx_snr; // keep the most recent SNR we received for this node. - - info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT - - // If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway - if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) { - info->has_hops_away = true; - info->hops_away = mp.hop_start - mp.hop_limit; - } - sortMeshDB(); } + FUNCTION_END; } void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId) { + FUNCTION_START("set_favorite"); meshtastic_NodeInfoLite *lite = _getMeshNode(nodeId); if (lite && lite->is_favorite != is_favorite) { lite->is_favorite = is_favorite; sortMeshDB(); saveNodeDatabaseToDisk(); } + FUNCTION_END; } +// returns true if nodeId is_favorite; false if not or not found bool NodeDB::isFavorite(uint32_t nodeId) { - // returns true if nodeId is_favorite; false if not or not found - + FUNCTION_START("set_favorite"); // NODENUM_BROADCAST will never be in the DB - if (nodeId == NODENUM_BROADCAST) + if (nodeId == NODENUM_BROADCAST) { + FUNCTION_END; return false; + } meshtastic_NodeInfoLite *lite = _getMeshNode(nodeId); if (lite) { + FUNCTION_END; return lite->is_favorite; } + FUNCTION_END; return false; } bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p) { + FUNCTION_START("isFromOrToFavoritedNode"); // This method is logically equivalent to: // return isFavorite(p.from) || isFavorite(p.to); // but is more efficient by: @@ -1745,32 +1773,41 @@ bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p) lite = &meshNodes->at(i); if (!seenFrom && lite->num == p.from) { - if (lite->is_favorite) + if (lite->is_favorite) { + FUNCTION_END; return true; + } seenFrom = true; } if (!seenTo && lite->num == p.to) { - if (lite->is_favorite) + if (lite->is_favorite) { + FUNCTION_END; return true; + } seenTo = true; } - if (seenFrom && seenTo) + if (seenFrom && seenTo) { + FUNCTION_END; return false; // we've seen both, and neither is a favorite, so we can stop searching early + } // Note: if we knew that sortMeshDB was always called after any change to is_favorite, we could exit early after searching // all favorited nodes first. } - + FUNCTION_END; return false; } void NodeDB::pause_sort(bool paused) { + // Including the mutex macro for completeness, but it's possible it isn't appropriate here + FUNCTION_START("pause_sort"); sortingIsPaused = paused; + FUNCTION_END; } void NodeDB::sortMeshDB() @@ -1805,10 +1842,13 @@ void NodeDB::sortMeshDB() uint8_t NodeDB::getMeshNodeChannel(NodeNum n) { + FUNCTION_START("getMeshNodeChannel"); const meshtastic_NodeInfoLite *info = _getMeshNode(n); if (!info) { + FUNCTION_END; return 0; // defaults to PRIMARY } + FUNCTION_END; return info->channel; } @@ -1894,18 +1934,25 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) /// valid lat/lon bool NodeDB::hasValidPosition(const meshtastic_NodeInfoLite *n) { - return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0); + FUNCTION_START("hasValidPosition"); + auto retVal = n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0); + FUNCTION_END; + return retVal; } /// If we have a node / user and they report is_licensed = true /// we consider them licensed UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum) { + FUNCTION_START("getLicenseStatus"); meshtastic_NodeInfoLite *info = _getMeshNode(nodeNum); if (!info || !info->has_user) { + FUNCTION_END; return UserLicenseStatus::NotKnown; } - return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed; + auto retVal = info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed; + FUNCTION_END; + return retVal; } bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest) @@ -1925,6 +1972,7 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location) { + FUNCTION_START("backupPreferences"); bool success = false; lastBackupAttempt = millis(); #ifdef FSCom @@ -1958,11 +2006,13 @@ bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location) // TODO: After more mainline SD card support } #endif + FUNCTION_END; return success; } bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, int restoreWhat) { + FUNCTION_START("backupPreferences"); bool success = false; #ifdef FSCom if (location == meshtastic_AdminMessage_BackupLocation_FLASH) { @@ -1970,6 +2020,7 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, if (!FSCom.exists(backupFileName)) { spiLock->unlock(); LOG_WARN("Could not restore. No backup file found"); + FUNCTION_END; return false; } else { spiLock->unlock(); @@ -2007,6 +2058,7 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, } else if (location == meshtastic_AdminMessage_BackupLocation_SD) { // TODO: After more mainline SD card support } + FUNCTION_END; return success; #endif } diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 229f6b14e..3c41b7db8 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -18,6 +18,13 @@ #include "PortduinoGlue.h" #endif +#define FUNCTION_START(FUNCTION_NAME) \ + if (fakeMutex) \ + LOG_ERROR("Concurrency violation in " FUNCTION_NAME); \ + fakeMutex = true; + +#define FUNCTION_END fakeMutex = false; + #if !defined(MESHTASTIC_EXCLUDE_PKI) // E3B0C442 is the blank hash static const uint8_t LOW_ENTROPY_HASHES[][32] = { @@ -139,7 +146,10 @@ class NodeDB bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS | SEGMENT_NODEDATABASE) { - return _saveToDisk(saveWhat); + FUNCTION_START("saveToDisk"); + auto retVal = _saveToDisk(saveWhat); + FUNCTION_END; + return retVal; } /** Reinit radio config if needed, because either: @@ -148,7 +158,12 @@ class NodeDB * * @param is_fresh_install set to true after a fresh install, to trigger NodeInfo/Position requests */ - void resetRadioConfig(bool is_fresh_install = false) { _resetRadioConfig(is_fresh_install); } + void resetRadioConfig(bool is_fresh_install = false) + { + FUNCTION_START("resetRadioConfig"); + _resetRadioConfig(is_fresh_install); + FUNCTION_END; + } /// given a subpacket sniffed from the network, update our DB state /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw @@ -195,7 +210,13 @@ class NodeDB std::string getNodeId() const; // @return last byte of a NodeNum, 0xFF if it ended at 0x00 - uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); } + uint8_t getLastByteOfNodeNum(NodeNum num) + { + FUNCTION_START("getLastByteOfNodeNum"); + auto retVal = (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); + FUNCTION_END; + return retVal; + } /// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use // bool handleWantNodeNum(NodeNum n); @@ -214,33 +235,77 @@ class NodeDB /* Return the number of nodes we've heard from recently (within the last 2 hrs?) * @param localOnly if true, ignore nodes heard via MQTT */ - size_t getNumOnlineMeshNodes(bool localOnly = false) { return _getNumOnlineMeshNodes(localOnly); } + size_t getNumOnlineMeshNodes(bool localOnly = false) + { + FUNCTION_START("getNumOnlineMeshNodes"); + auto retVal = _getNumOnlineMeshNodes(localOnly); + FUNCTION_END; + return retVal; + } void resetNodes(), removeNodeByNum(NodeNum nodeNum); bool factoryReset(bool eraseBleBonds = false); - void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) { _installRoleDefaults(role); } + void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) + { + FUNCTION_START("installRoleDefaults"); + _installRoleDefaults(role); + FUNCTION_END; + } const meshtastic_NodeInfoLite *readNextMeshNode(uint32_t &readIndex); meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x) { - assert(x < numMeshNodes); - return &meshNodes->at(x); + FUNCTION_START("getMeshNodeByIndex"); + meshtastic_NodeInfoLite *retValue = nullptr; + if (x < numMeshNodes) + retValue = &meshNodes->at(x); + FUNCTION_END; + return retValue; } - meshtastic_NodeInfoLite *getMeshNode(NodeNum n) { return getMeshNode(n); } - size_t getNumMeshNodes() { return numMeshNodes; } + meshtastic_NodeInfoLite *getMeshNode(NodeNum n) + { + FUNCTION_START("getMeshNode"); + auto retVal = _getMeshNode(n); + FUNCTION_END; + return retVal; + } + + size_t getNumMeshNodes() + { + FUNCTION_START("getNumMeshNodes"); + auto retVal = numMeshNodes; + FUNCTION_END; + return retVal; + } UserLicenseStatus getLicenseStatus(uint32_t nodeNum); // returns true if the maximum number of nodes is reached or we are running low on memory - bool isFull() { return _isFull(); } + bool isFull() + { + FUNCTION_START("isFull"); + auto retVal = _isFull(); + FUNCTION_END; + return retVal; + } - void clearLocalPosition() { _clearLocalPosition(); } + void clearLocalPosition() + { + FUNCTION_START("clearLocalPosition"); + _clearLocalPosition(); + FUNCTION_END; + } - void setLocalPosition(meshtastic_Position position, bool timeOnly = false) { _setLocalPosition(position, timeOnly); } + void setLocalPosition(meshtastic_Position position, bool timeOnly = false) + { + FUNCTION_START("setLocalPosition"); + _setLocalPosition(position, timeOnly); + FUNCTION_END; + } bool hasValidPosition(const meshtastic_NodeInfoLite *n); @@ -248,14 +313,21 @@ class NodeDB bool restorePreferences(meshtastic_AdminMessage_BackupLocation location, int restoreWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS); - void notifyObservers(bool forceUpdate = false) { _notifyObservers(forceUpdate); } + void notifyObservers(bool forceUpdate = false) + { + FUNCTION_START("notifyObservers"); + _notifyObservers(forceUpdate); + FUNCTION_END; + } private: + bool fakeMutex = false; + /// Notify observers of changes to the DB void _notifyObservers(bool forceUpdate = false) { // Notify observers of the current node state - const meshtastic::NodeStatus status = meshtastic::NodeStatus(_getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate); + const meshtastic::NodeStatus status = meshtastic::NodeStatus(_getNumOnlineMeshNodes(), numMeshNodes, forceUpdate); newStatus.notifyObservers(&status); } From d772cd443146d48e09625c5067504e7be35fc0d3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 17 Oct 2025 12:16:59 -0500 Subject: [PATCH 6/6] Retain getMeshNode virtual marker for tests --- src/mesh/NodeDB.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 3c41b7db8..0219de21f 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -266,7 +266,7 @@ class NodeDB return retValue; } - meshtastic_NodeInfoLite *getMeshNode(NodeNum n) + virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n) { FUNCTION_START("getMeshNode"); auto retVal = _getMeshNode(n);