diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index f215be80fb..a57233e9d5 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 fdc0b76ecd..eb4ada8e71 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 701062e082..4677b08917 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/InkHUD/Applets/User/Heard/HeardApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp index 5a659c6067..aeaa78cda3 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) { diff --git a/src/graphics/niche/Utils/CannedMessageStore.cpp b/src/graphics/niche/Utils/CannedMessageStore.cpp index 50998930d7..cf1533c627 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 dec8411fec..af71da7337 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" @@ -314,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 @@ -425,13 +424,13 @@ 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 } #endif sortMeshDB(); - saveToDisk(saveWhat); + _saveToDisk(saveWhat); } /** @@ -460,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++; @@ -480,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(); @@ -498,7 +498,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 @@ -513,6 +513,7 @@ bool NodeDB::factoryReset(bool eraseBleBonds) Bluefruit.Central.clearBonds(); #endif } + FUNCTION_END; return true; } @@ -649,7 +650,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) || \ @@ -736,7 +737,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(); @@ -894,7 +895,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(); @@ -980,6 +981,7 @@ void NodeDB::installDefaultChannels() void NodeDB::resetNodes() { + FUNCTION_START("resetNodes"); if (!config.position.fixed_position) clearLocalPosition(); numMeshNodes = 1; @@ -990,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) @@ -1006,16 +1010,17 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum) meshtastic_NodeInfoLite()); LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed); saveNodeDatabaseToDisk(); + FUNCTION_END; } -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; node->position.time = 0; - setLocalPosition(meshtastic_Position_init_default); + _setLocalPosition(meshtastic_Position_init_default); } void NodeDB::cleanupMeshDB() @@ -1091,7 +1096,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) @@ -1105,39 +1110,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 @@ -1237,7 +1209,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. @@ -1288,7 +1260,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), @@ -1340,7 +1312,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 @@ -1351,34 +1323,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 @@ -1467,7 +1411,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); @@ -1491,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 @@ -1522,7 +1468,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; @@ -1544,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; } @@ -1554,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 @@ -1581,7 +1529,8 @@ 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 + FUNCTION_END; } /** Update telemetry info for this node based on received metrics @@ -1589,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; } @@ -1604,7 +1555,8 @@ 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 + FUNCTION_END; } /** @@ -1612,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 @@ -1622,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; } } @@ -1646,22 +1601,26 @@ 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(); + 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; } #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 @@ -1678,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; } } @@ -1685,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!"); @@ -1712,19 +1673,19 @@ 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 if (!Throttle::isWithinTimespanMs(lastNodeDbSave, ONE_MINUTE_MS)) { - saveToDisk(SEGMENT_NODEDATABASE); + _saveToDisk(SEGMENT_NODEDATABASE); lastNodeDbSave = millis(); } else { LOG_DEBUG("Defer NodeDB saveToDisk for now"); } } - + FUNCTION_END; return changed; } @@ -1732,107 +1693,121 @@ 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 (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard - info->last_heard = mp.rx_time; + 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. + 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 + 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; + // 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(); } - sortMeshDB(); } + FUNCTION_END; } void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId) { - meshtastic_NodeInfoLite *lite = getMeshNode(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); + 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: // 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 (lite->is_favorite) + if (!seenFrom && lite->num == p.from) { + if (lite->is_favorite) { + FUNCTION_END; return true; + } seenFrom = true; } - if (lite->num == p.to) { - if (lite->is_favorite) + if (!seenTo && lite->num == p.to) { + 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() @@ -1867,10 +1842,13 @@ void NodeDB::sortMeshDB() uint8_t NodeDB::getMeshNodeChannel(NodeNum n) { - const meshtastic_NodeInfoLite *info = getMeshNode(n); + FUNCTION_START("getMeshNodeChannel"); + const meshtastic_NodeInfoLite *info = _getMeshNode(n); if (!info) { + FUNCTION_END; return 0; // defaults to PRIMARY } + FUNCTION_END; return info->channel; } @@ -1883,7 +1861,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) @@ -1893,7 +1871,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); } @@ -1901,7 +1879,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()) { @@ -1956,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) { - meshtastic_NodeInfoLite *info = getMeshNode(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) @@ -1987,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 @@ -2020,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) { @@ -2032,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(); @@ -2057,7 +2046,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 { @@ -2069,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 e8724f2c95..0219de21f1 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] = { @@ -110,19 +117,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 @@ -135,7 +129,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; @@ -151,17 +144,26 @@ 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) + { + FUNCTION_START("saveToDisk"); + auto retVal = _saveToDisk(saveWhat); + FUNCTION_END; + return retVal; + } /** 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) + { + 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 @@ -208,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); @@ -227,76 +235,104 @@ 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) + { + FUNCTION_START("getNumOnlineMeshNodes"); + auto retVal = _getNumOnlineMeshNodes(localOnly); + FUNCTION_END; + return retVal; + } - void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum); + void resetNodes(), removeNodeByNum(NodeNum nodeNum); 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); + 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; } - virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n); - size_t getNumMeshNodes() { return numMeshNodes; } - - UserLicenseStatus getLicenseStatus(uint32_t nodeNum); + virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n) + { + FUNCTION_START("getMeshNode"); + auto retVal = _getMeshNode(n); + FUNCTION_END; + return retVal; + } - size_t getMaxNodesAllocatedSize() + size_t getNumMeshNodes() { - 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); + 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(); + bool isFull() + { + FUNCTION_START("isFull"); + auto retVal = _isFull(); + FUNCTION_END; + return retVal; + } - void clearLocalPosition(); + void clearLocalPosition() + { + FUNCTION_START("clearLocalPosition"); + _clearLocalPosition(); + FUNCTION_END; + } 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; + FUNCTION_START("setLocalPosition"); + _setLocalPosition(position, timeOnly); + FUNCTION_END; } 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); - /// Notify observers of changes to the DB 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); } - 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 @@ -330,6 +366,51 @@ class NodeDB bool saveDeviceStateToDisk(); bool saveNodeDatabaseToDisk(); void sortMeshDB(); + + 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 | + 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); + + 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; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index d300ff53b8..adf74fef52 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 9f95a9e209..526b47dc11 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -2187,9 +2187,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(); } } @@ -2209,8 +2209,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 ffc789275b..a97a137ebf 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); } }