This commit is contained in:
Jonathan Bennett 2025-10-26 01:55:37 -04:00 committed by GitHub
commit 755cdb9c6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 358 additions and 216 deletions

View File

@ -10,7 +10,10 @@
*/
#include "FSCommon.h"
#include "SPILock.h"
#include "SafeFile.h"
#include "configuration.h"
#include <pb_decode.h>
#include <pb_encode.h>
// 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)
@ -336,3 +339,62 @@ void setupSDCard()
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<Print *>(&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;
}

View File

@ -3,6 +3,19 @@
#include "configuration.h"
#include <vector>
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)
@ -56,3 +69,7 @@ std::vector<meshtastic_FileInfo> 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();
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);

View File

@ -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

View File

@ -62,22 +62,12 @@ void InkHUD::HeardApplet::populateFromNodeDB()
{
// Fill a collection with pointers to each node in db
std::vector<meshtastic_NodeInfoLite *> 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) {

View File

@ -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)

View File

@ -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<Print *>(&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 (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)
{
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
}

View File

@ -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<meshtastic_NodeInfoLite> *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<const meshtastic::NodeStatus *> 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; }
virtual 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);
size_t getMaxNodesAllocatedSize()
// returns true if the maximum number of nodes is reached or we are running low on memory
bool isFull()
{
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("isFull");
auto retVal = _isFull();
FUNCTION_END;
return retVal;
}
// returns true if the maximum number of nodes is reached or we are running low on memory
bool isFull();
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)
{
// Notify observers of the current node state
const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate);
newStatus.notifyObservers(&status);
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(), numMeshNodes, forceUpdate);
newStatus.notifyObservers(&status);
}
std::vector<meshtastic_NodeInfoLite> *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;

View File

@ -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)

View File

@ -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;
}

View File

@ -14,6 +14,7 @@
* @date [Insert Date]
*/
#include "ExternalNotificationModule.h"
#include "FSCommon.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
@ -369,8 +370,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));
@ -626,7 +627,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);
}
}