diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index cdf662e1f..8ab9166fb 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,6 +1,6 @@ version: 0.1 cli: - version: 1.9.1 + version: 1.10.0 plugins: sources: - id: trunk @@ -10,7 +10,7 @@ lint: enabled: - taplo@0.7.0 - ruff@0.0.265 - - yamllint@1.31.0 + - yamllint@1.32.0 - isort@5.12.0 - markdownlint@0.34.0 - oxipng@8.0.0 diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 83bd0e325..db3f3f35e 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -49,10 +49,13 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas tosend->hop_limit--; // bump down the hop count - // If it is a traceRoute request, update the route that it went via me - if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && traceRouteModule && - traceRouteModule->wantPacket(p)) { - traceRouteModule->updateRoute(tosend); + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + // If it is a traceRoute request, update the route that it went via me + if (traceRouteModule && traceRouteModule->wantPacket(p)) + traceRouteModule->updateRoute(tosend); + // If it is a neighborInfo packet, update last_sent_by_id + if (neighborInfoModule && neighborInfoModule->wantPacket(p)) + neighborInfoModule->updateLastSentById(tosend); } LOG_INFO("Rebroadcasting received floodmsg to neighbors\n"); diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 563b850a5..309035cb3 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -2,6 +2,7 @@ #include "PacketHistory.h" #include "Router.h" +#include "modules/NeighborInfoModule.h" #include "modules/TraceRouteModule.h" /** @@ -57,4 +58,4 @@ class FloodingRouter : public Router, protected PacketHistory * Look for broadcasts we need to rebroadcast */ virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override; -}; +}; \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 38f4960b6..e7526505a 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -15,6 +15,7 @@ #include "error.h" #include "main.h" #include "mesh-pb-constants.h" +#include "modules/NeighborInfoModule.h" #include #include #include @@ -63,11 +64,7 @@ uint32_t error_address = 0; static uint8_t ourMacAddr[6]; -NodeDB::NodeDB() - : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count), meshNodes(devicestate.node_db_lite), - numMeshNodes(&devicestate.node_db_lite_count) -{ -} +NodeDB::NodeDB() : meshNodes(devicestate.node_db_lite), numMeshNodes(&devicestate.node_db_lite_count) {} /** * Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on @@ -248,6 +245,9 @@ void NodeDB::installDefaultModuleConfig() strncpy(moduleConfig.mqtt.username, default_mqtt_username, sizeof(moduleConfig.mqtt.username)); strncpy(moduleConfig.mqtt.password, default_mqtt_password, sizeof(moduleConfig.mqtt.password)); + moduleConfig.has_neighbor_info = true; + moduleConfig.neighbor_info.enabled = false; + initModuleConfigIntervals(); } @@ -271,6 +271,7 @@ void NodeDB::initModuleConfigIntervals() moduleConfig.telemetry.device_update_interval = default_broadcast_interval_secs; moduleConfig.telemetry.environment_update_interval = default_broadcast_interval_secs; moduleConfig.telemetry.air_quality_interval = default_broadcast_interval_secs; + moduleConfig.neighbor_info.update_interval = default_broadcast_interval_secs; } void NodeDB::installDefaultChannels() @@ -282,12 +283,11 @@ void NodeDB::installDefaultChannels() void NodeDB::resetNodes() { - devicestate.node_db_count = 0; - memset(devicestate.node_db, 0, sizeof(devicestate.node_db)); - devicestate.node_db_lite_count = 0; memset(devicestate.node_db_lite, 0, sizeof(devicestate.node_db_lite)); saveDeviceStateToDisk(); + if (neighborInfoModule && moduleConfig.neighbor_info.enabled) + neighborInfoModule->resetNeighbors(); } void NodeDB::installDefaultDeviceState() @@ -295,12 +295,11 @@ void NodeDB::installDefaultDeviceState() LOG_INFO("Installing default DeviceState\n"); memset(&devicestate, 0, sizeof(meshtastic_DeviceState)); - *numNodes = 0; + *numMeshNodes = 0; // init our devicestate with valid flags so protobuf writing/reading will work devicestate.has_my_node = true; devicestate.has_owner = true; - devicestate.node_db_count = 0; devicestate.node_db_lite_count = 0; devicestate.version = DEVICESTATE_CUR_VER; devicestate.receive_queue_count = 0; // Not yet implemented FIXME @@ -328,11 +327,9 @@ void NodeDB::init() int saveWhat = 0; // likewise - we always want the app requirements to come from the running appload - myNodeInfo.min_app_version = 20300; // format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20 - myNodeInfo.max_channels = MAX_NUM_CHANNELS; // tell others the max # of channels we can understand + myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00 // Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't // keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts) - strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version)); pickNewNodeNum(); // Set our board type so we can share it with others @@ -343,19 +340,6 @@ void NodeDB::init() info->user = owner; info->has_user = true; - if (*numNodes > 0) { - LOG_DEBUG("Legacy NodeDB detected... Migrating to NodeDBLite\n"); - uint32_t readIndex = 0; - const meshtastic_NodeInfo *oldNodeInfo = nodeDB.readNextNodeInfo(readIndex); - while (oldNodeInfo != NULL) { - migrateToNodeInfoLite(oldNodeInfo); - oldNodeInfo = nodeDB.readNextNodeInfo(readIndex); - } - LOG_DEBUG("Migration complete! Clearing out legacy NodeDB...\n"); - devicestate.node_db_count = 0; - memset(devicestate.node_db, 0, sizeof(devicestate.node_db)); - } - #ifdef ARCH_ESP32 Preferences preferences; preferences.begin("meshtastic", false); @@ -365,7 +349,7 @@ void NodeDB::init() #endif resetRadioConfig(); // If bogus settings got saved, then fix them - LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, *numNodes); + LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, *numMeshNodes); if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate))) saveWhat |= SEGMENT_DEVICESTATE; @@ -611,14 +595,6 @@ void NodeDB::saveToDisk(int saveWhat) } } -const meshtastic_NodeInfo *NodeDB::readNextNodeInfo(uint32_t &readIndex) -{ - if (readIndex < *numNodes) - return &nodes[readIndex++]; - else - return NULL; -} - const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex) { if (readIndex < *numMeshNodes) @@ -796,17 +772,6 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n) return info->channel; } -/// Find a node in our DB, return null for missing -/// NOTE: This function might be called from an ISR -meshtastic_NodeInfo *NodeDB::getNodeInfo(NodeNum n) -{ - for (int i = 0; i < *numNodes; i++) - if (nodes[i].num == n) - return &nodes[i]; - - return NULL; -} - /// 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) @@ -852,57 +817,6 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) return lite; } -void NodeDB::migrateToNodeInfoLite(const meshtastic_NodeInfo *node) -{ - meshtastic_NodeInfoLite *lite = getMeshNode(node->num); - - if (!lite) { - if ((*numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { - screen->print("warning: node_db_lite full! erasing oldest entry\n"); - // look for oldest node and erase it - uint32_t oldest = UINT32_MAX; - int oldestIndex = -1; - for (int i = 0; i < *numMeshNodes; i++) { - if (meshNodes[i].last_heard < oldest) { - oldest = meshNodes[i].last_heard; - oldestIndex = i; - } - } - // Shove the remaining nodes down the chain - for (int i = oldestIndex; i < *numMeshNodes - 1; i++) { - meshNodes[i] = meshNodes[i + 1]; - } - (*numMeshNodes)--; - } - // add the node at the end - lite = &meshNodes[(*numMeshNodes)++]; - - // everything is missing except the nodenum - memset(lite, 0, sizeof(*lite)); - lite->num = node->num; - lite->snr = node->snr; - lite->last_heard = node->last_heard; - lite->channel = node->channel; - - if (node->has_position) { - lite->has_position = true; - lite->position.latitude_i = node->position.latitude_i; - lite->position.longitude_i = node->position.longitude_i; - lite->position.altitude = node->position.altitude; - lite->position.location_source = node->position.location_source; - lite->position.time = node->position.time; - } - if (node->has_user) { - lite->has_user = true; - lite->user = node->user; - } - if (node->has_device_metrics) { - lite->has_device_metrics = true; - lite->device_metrics = node->device_metrics; - } - } -} - /// Record an error that should be reported via analytics void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, const char *filename) { diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 0816af817..ab0ec8288 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -19,7 +19,7 @@ DeviceState versions used to be defined in the .proto file but really only this #define SEGMENT_DEVICESTATE 4 #define SEGMENT_CHANNELS 8 -#define DEVICESTATE_CUR_VER 20 +#define DEVICESTATE_CUR_VER 22 #define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER extern meshtastic_DeviceState devicestate; @@ -45,9 +45,6 @@ class NodeDB // Eventually use a smarter datastructure // HashMap nodes; // Note: these two references just point into our static array we serialize to/from disk - meshtastic_NodeInfo *nodes; - pb_size_t *numNodes; - meshtastic_NodeInfoLite *meshNodes; pb_size_t *numMeshNodes; @@ -137,18 +134,6 @@ class NodeDB private: /// Find a node in our DB, create an empty NodeInfoLite if missing meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n); - void migrateToNodeInfoLite(const meshtastic_NodeInfo *node); - /// Find a node in our DB, return null for missing - meshtastic_NodeInfo *getNodeInfo(NodeNum n); - /// Allow the bluetooth layer to read our next nodeinfo record, or NULL if done reading - const meshtastic_NodeInfo *readNextNodeInfo(uint32_t &readIndex); - size_t getNumNodes() { return *numNodes; } - - meshtastic_NodeInfo *getNodeByIndex(size_t x) - { - assert(x < *numNodes); - return &nodes[x]; - } /// Notify observers of changes to the DB void notifyObservers(bool forceUpdate = false) @@ -226,10 +211,6 @@ inline uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t d /// Sometimes we will have Position objects that only have a time, so check for /// valid lat/lon -static inline bool hasValidPosition(const meshtastic_NodeInfo *n) -{ - return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0); -} static inline bool hasValidPosition(const meshtastic_NodeInfoLite *n) { return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0); diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 6c6c70165..cd230cb1c 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -279,6 +279,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_remote_hardware_tag; fromRadioScratch.moduleConfig.payload_variant.remote_hardware = moduleConfig.remote_hardware; break; + case meshtastic_ModuleConfig_neighbor_info_tag: + fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_neighbor_info_tag; + fromRadioScratch.moduleConfig.payload_variant.neighbor_info = moduleConfig.neighbor_info; + break; default: LOG_ERROR("Unknown module config type %d\n", config_state); } diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index 8e22aa82b..22a80c8e3 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -16,7 +16,7 @@ #define MAX_RX_TOPHONE 32 /// max number of nodes allowed in the mesh -#define MAX_NUM_NODES (member_size(meshtastic_DeviceState, node_db) / member_size(meshtastic_DeviceState, node_db[0])) +#define MAX_NUM_NODES (member_size(meshtastic_DeviceState, node_db_lite) / member_size(meshtastic_DeviceState, node_db_lite[0])) /// Max number of channels allowed #define MAX_NUM_CHANNELS (member_size(meshtastic_ChannelFile, channels) / member_size(meshtastic_ChannelFile, channels[0])) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index be76f62a5..283375b23 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -368,6 +368,11 @@ void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) moduleConfig.has_remote_hardware = true; moduleConfig.remote_hardware = c.payload_variant.remote_hardware; break; + case meshtastic_ModuleConfig_neighbor_info_tag: + LOG_INFO("Setting module config: Neighbor Info\n"); + moduleConfig.has_neighbor_info = true; + moduleConfig.neighbor_info = c.payload_variant.neighbor_info; + break; } saveChanges(SEGMENT_MODULECONFIG); @@ -503,6 +508,11 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_remote_hardware_tag; res.get_module_config_response.payload_variant.remote_hardware = moduleConfig.remote_hardware; break; + case meshtastic_AdminMessage_ModuleConfigType_NEIGHBORINFO_CONFIG: + LOG_INFO("Getting module config: Neighbor Info\n"); + res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_neighbor_info_tag; + res.get_module_config_response.payload_variant.neighbor_info = moduleConfig.neighbor_info; + break; } // NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior. @@ -663,4 +673,4 @@ AdminModule::AdminModule() : ProtobufModule("Admin", meshtastic_PortNum_ADMIN_AP { // restrict to the admin channel for rx boundChannel = Channels::adminChannel; -} +} \ No newline at end of file diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 241434e52..9c216ff72 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -7,6 +7,7 @@ #include "input/kbMatrixImpl.h" #include "modules/AdminModule.h" #include "modules/CannedMessageModule.h" +#include "modules/NeighborInfoModule.h" #include "modules/NodeInfoModule.h" #include "modules/PositionModule.h" #include "modules/RemoteHardwareModule.h" @@ -48,6 +49,7 @@ void setupModules() waypointModule = new WaypointModule(); textMessageModule = new TextMessageModule(); traceRouteModule = new TraceRouteModule(); + neighborInfoModule = new NeighborInfoModule(); // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance // to a global variable. diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp new file mode 100644 index 000000000..578b41b29 --- /dev/null +++ b/src/modules/NeighborInfoModule.cpp @@ -0,0 +1,319 @@ +#include "NeighborInfoModule.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "RTC.h" + +#define MAX_NUM_NEIGHBORS 10 // also defined in NeighborInfo protobuf options +NeighborInfoModule *neighborInfoModule; + +static const char *neighborInfoConfigFile = "/prefs/neighbors.proto"; + +/* +Prints a single neighbor info packet and associated neighbors +Uses LOG_DEBUG, which equates to Console.log +NOTE: For debugging only +*/ +void NeighborInfoModule::printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np) +{ + LOG_DEBUG("%s NEIGHBORINFO PACKET from Node %d to Node %d (last sent by %d)\n", header, np->node_id, nodeDB.getNodeNum(), + np->last_sent_by_id); + LOG_DEBUG("----------------\n"); + LOG_DEBUG("Packet contains %d neighbors\n", np->neighbors_count); + for (int i = 0; i < np->neighbors_count; i++) { + LOG_DEBUG("Neighbor %d: node_id=%d, snr=%.2f\n", i, np->neighbors[i].node_id, np->neighbors[i].snr); + } + LOG_DEBUG("----------------\n"); +} +/* +Prints the nodeDB nodes so we can see whose nodeInfo we have +NOTE: for debugging only +*/ +void NeighborInfoModule::printNodeDBNodes(const char *header) +{ + int num_nodes = nodeDB.getNumMeshNodes(); + LOG_DEBUG("%s NODEDB SELECTION from Node %d:\n", header, nodeDB.getNodeNum()); + LOG_DEBUG("----------------\n"); + LOG_DEBUG("DB contains %d nodes\n", num_nodes); + for (int i = 0; i < num_nodes; i++) { + meshtastic_NodeInfoLite *dbEntry = nodeDB.getMeshNodeByIndex(i); + LOG_DEBUG(" Node %d: node_id=%d, snr=%.2f\n", i, dbEntry->num, dbEntry->snr); + } + LOG_DEBUG("----------------\n"); +} + +/* +Prints the nodeDB neighbors +NOTE: for debugging only +*/ +void NeighborInfoModule::printNodeDBNeighbors(const char *header) +{ + int num_neighbors = getNumNeighbors(); + LOG_DEBUG("%s NODEDB SELECTION from Node %d:\n", header, nodeDB.getNodeNum()); + LOG_DEBUG("----------------\n"); + LOG_DEBUG("DB contains %d neighbors\n", num_neighbors); + for (int i = 0; i < num_neighbors; i++) { + meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); + LOG_DEBUG(" Node %d: node_id=%d, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr); + } + LOG_DEBUG("----------------\n"); +} + +/* +Prints the nodeDB with selectors for the neighbors we've chosen to send (inefficiently) +Uses LOG_DEBUG, which equates to Console.log +NOTE: For debugging only +*/ +void NeighborInfoModule::printNodeDBSelection(const char *header, const meshtastic_NeighborInfo *np) +{ + int num_neighbors = getNumNeighbors(); + LOG_DEBUG("%s NODEDB SELECTION from Node %d:\n", header, nodeDB.getNodeNum()); + LOG_DEBUG("----------------\n"); + LOG_DEBUG("Selected %d neighbors of %d DB neighbors\n", np->neighbors_count, num_neighbors); + for (int i = 0; i < num_neighbors; i++) { + meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); + bool chosen = false; + for (int j = 0; j < np->neighbors_count; j++) { + if (np->neighbors[j].node_id == dbEntry->node_id) { + chosen = true; + } + } + if (!chosen) { + LOG_DEBUG(" Node %d: neighbor=%d, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr); + } else { + LOG_DEBUG("---> Node %d: neighbor=%d, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr); + } + } + LOG_DEBUG("----------------\n"); +} + +/* Send our initial owner announcement 35 seconds after we start (to give network time to setup) */ +NeighborInfoModule::NeighborInfoModule() + : neighbors(neighborState.neighbors), numNeighbors(&neighborState.neighbors_count), + ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg), concurrency::OSThread( + "NeighborInfoModule") +{ + ourPortNum = meshtastic_PortNum_NEIGHBORINFO_APP; + + if (moduleConfig.neighbor_info.enabled) { + this->loadProtoForModule(); + setIntervalFromNow(35 * 1000); + } else { + LOG_DEBUG("NeighborInfoModule is disabled\n"); + disable(); + } +} + +/* +Allocate a zeroed neighbor info packet +*/ +meshtastic_NeighborInfo *NeighborInfoModule::allocateNeighborInfoPacket() +{ + meshtastic_NeighborInfo *neighborInfo = (meshtastic_NeighborInfo *)malloc(sizeof(meshtastic_NeighborInfo)); + memset(neighborInfo, 0, sizeof(meshtastic_NeighborInfo)); + return neighborInfo; +} + +/* +Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time +Assumes that the neighborInfo packet has been allocated +@returns the number of entries collected +*/ +uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo) +{ + int my_node_id = nodeDB.getNodeNum(); + neighborInfo->node_id = my_node_id; + neighborInfo->last_sent_by_id = my_node_id; + neighborInfo->node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; + + int num_neighbors = cleanUpNeighbors(); + + for (int i = 0; i < num_neighbors; i++) { + meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); + if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (dbEntry->node_id != my_node_id)) { + neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = dbEntry->node_id; + neighborInfo->neighbors[neighborInfo->neighbors_count].snr = dbEntry->snr; + // Note: we don't set the last_rx_time and node_broadcast_intervals_secs here, because we don't want to send this over + // the mesh + neighborInfo->neighbors_count++; + } + } + printNodeDBNodes("DBSTATE"); + printNodeDBNeighbors("NEIGHBORS"); + printNodeDBSelection("COLLECTED", neighborInfo); + return neighborInfo->neighbors_count; +} + +/* +Remove neighbors from the database that we haven't heard from in a while +@returns new number of neighbors +*/ +size_t NeighborInfoModule::cleanUpNeighbors() +{ + uint32_t now = getTime(); + int num_neighbors = getNumNeighbors(); + NodeNum my_node_id = nodeDB.getNodeNum(); + + // Find neighbors to remove + std::vector indices_to_remove; + for (int i = 0; i < num_neighbors; i++) { + meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); + // We will remove a neighbor if we haven't heard from them in twice the broadcast interval + if ((now - dbEntry->last_rx_time > dbEntry->node_broadcast_interval_secs * 2) && (dbEntry->node_id != my_node_id)) { + indices_to_remove.push_back(i); + } + } + + // Update the neighbor list + for (int i = 0; i < indices_to_remove.size(); i++) { + int index = indices_to_remove[i]; + LOG_DEBUG("Removing neighbor with node ID 0x%x\n", neighbors[index].node_id); + for (int j = index; j < num_neighbors - 1; j++) { + neighbors[j] = neighbors[j + 1]; + } + (*numNeighbors)--; + } + + // Save the neighbor list if we removed any neighbors + if (indices_to_remove.size() > 0) { + saveProtoForModule(); + } + + return *numNeighbors; +} + +/* Send neighbor info to the mesh */ +void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies) +{ + meshtastic_NeighborInfo *neighborInfo = allocateNeighborInfoPacket(); + collectNeighborInfo(neighborInfo); + meshtastic_MeshPacket *p = allocDataProtobuf(*neighborInfo); + // send regardless of whether or not we have neighbors in our DB, + // because we want to get neighbors for the next cycle + p->to = dest; + p->decoded.want_response = wantReplies; + printNeighborInfo("SENDING", neighborInfo); + service.sendToMesh(p, RX_SRC_LOCAL, true); +} + +/* +Encompasses the full construction and sending packet to mesh +Will be used for broadcast. +*/ +int32_t NeighborInfoModule::runOnce() +{ + bool requestReplies = false; + sendNeighborInfo(NODENUM_BROADCAST, requestReplies); + return getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs); +} + +/* +Collect a recieved neighbor info packet from another node +Pass it to an upper client; do not persist this data on the mesh +*/ +bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np) +{ + printNeighborInfo("RECEIVED", np); + updateNeighbors(mp, np); + // Allow others to handle this packet + return false; +} + +/* +Copy the content of a current NeighborInfo packet into a new one and update the last_sent_by_id to our NodeNum +*/ +void NeighborInfoModule::updateLastSentById(meshtastic_MeshPacket *p) +{ + auto &incoming = p->decoded; + meshtastic_NeighborInfo scratch; + meshtastic_NeighborInfo *updated = NULL; + memset(&scratch, 0, sizeof(scratch)); + pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, &meshtastic_NeighborInfo_msg, &scratch); + updated = &scratch; + + updated->last_sent_by_id = nodeDB.getNodeNum(); + + // Set updated last_sent_by_id to the payload of the to be flooded packet + p->decoded.payload.size = + pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_NeighborInfo_msg, updated); +} + +void NeighborInfoModule::resetNeighbors() +{ + *numNeighbors = 0; + neighborState.neighbors_count = 0; + memset(neighborState.neighbors, 0, sizeof(neighborState.neighbors)); + saveProtoForModule(); +} + +void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np) +{ + // The last sent ID will be 0 if the packet is from the phone, which we don't count as + // an edge. So we assume that if it's zero, then this packet is from our node. + if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) { + getOrCreateNeighbor(mp.from, np->last_sent_by_id, np->node_broadcast_interval_secs, mp.rx_snr); + } +} + +meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSender, NodeNum n, + uint32_t node_broadcast_interval_secs, int snr) +{ + // our node and the phone are the same node (not neighbors) + if (n == 0) { + n = nodeDB.getNodeNum(); + } + // look for one in the existing list + for (int i = 0; i < (*numNeighbors); i++) { + meshtastic_Neighbor *nbr = &neighbors[i]; + if (nbr->node_id == n) { + // if found, update it + nbr->snr = snr; + nbr->last_rx_time = getTime(); + // Only if this is the original sender, the broadcast interval corresponds to it + if (originalSender == n) + nbr->node_broadcast_interval_secs = node_broadcast_interval_secs; + saveProtoForModule(); // Save the updated neighbor + return nbr; + } + } + // otherwise, allocate one and assign data to it + // TODO: max memory for the database should take neighbors into account, but currently doesn't + if (*numNeighbors < MAX_NUM_NODES) { + (*numNeighbors)++; + } + meshtastic_Neighbor *new_nbr = &neighbors[((*numNeighbors) - 1)]; + new_nbr->node_id = n; + new_nbr->snr = snr; + new_nbr->last_rx_time = getTime(); + // Only if this is the original sender, the broadcast interval corresponds to it + if (originalSender == n) + new_nbr->node_broadcast_interval_secs = node_broadcast_interval_secs; + saveProtoForModule(); // Save the new neighbor + return new_nbr; +} + +void NeighborInfoModule::loadProtoForModule() +{ + if (!nodeDB.loadProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, sizeof(meshtastic_NeighborInfo), + &meshtastic_NeighborInfo_msg, &neighborState)) { + neighborState = meshtastic_NeighborInfo_init_zero; + } +} + +/** + * @brief Save the module config to file. + * + * @return true On success. + * @return false On error. + */ +bool NeighborInfoModule::saveProtoForModule() +{ + bool okay = true; + +#ifdef FS + FS.mkdir("/prefs"); +#endif + + okay &= nodeDB.saveProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, &meshtastic_NeighborInfo_msg, &neighborState); + + return okay; +} \ No newline at end of file diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h new file mode 100644 index 000000000..a2729f16e --- /dev/null +++ b/src/modules/NeighborInfoModule.h @@ -0,0 +1,84 @@ +#pragma once +#include "ProtobufModule.h" + +/* + * Neighborinfo module for sending info on each node's 0-hop neighbors to the mesh + */ +class NeighborInfoModule : public ProtobufModule, private concurrency::OSThread +{ + meshtastic_Neighbor *neighbors; + pb_size_t *numNeighbors; + + public: + /* + * Expose the constructor + */ + NeighborInfoModule(); + + /* Reset neighbor info after clearing nodeDB*/ + void resetNeighbors(); + + bool saveProtoForModule(); + + // Let FloodingRouter call updateLastSentById upon rebroadcasting a NeighborInfo packet + friend class FloodingRouter; + + protected: + // Note: this holds our local info. + meshtastic_NeighborInfo neighborState; + + /* + * Called to handle a particular incoming message + * @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *nb) override; + + /* + * Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time + * @return the number of entries collected + */ + uint32_t collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo); + + /* + Remove neighbors from the database that we haven't heard from in a while + @returns new number of neighbors + */ + size_t cleanUpNeighbors(); + + /* Allocate a new NeighborInfo packet */ + meshtastic_NeighborInfo *allocateNeighborInfoPacket(); + + // Find a neighbor in our DB, create an empty neighbor if missing + meshtastic_Neighbor *getOrCreateNeighbor(NodeNum originalSender, NodeNum n, uint32_t node_broadcast_interval_secs, int snr); + + /* + * Send info on our node's neighbors into the mesh + */ + void sendNeighborInfo(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); + + size_t getNumNeighbors() { return *numNeighbors; } + + meshtastic_Neighbor *getNeighborByIndex(size_t x) + { + assert(x < *numNeighbors); + return &neighbors[x]; + } + + /* update neighbors with subpacket sniffed from network */ + void updateNeighbors(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np); + + /* update a NeighborInfo packet with our NodeNum as last_sent_by_id */ + void updateLastSentById(meshtastic_MeshPacket *p); + + void loadProtoForModule(); + + /* Does our periodic broadcast */ + int32_t runOnce() override; + + /* These are for debugging only */ + void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np); + void printNodeDBNodes(const char *header); + void printNodeDBNeighbors(const char *header); + void printNodeDBSelection(const char *header, const meshtastic_NeighborInfo *np); +}; +extern NeighborInfoModule *neighborInfoModule; \ No newline at end of file diff --git a/src/mqtt/JSONValue.cpp b/src/mqtt/JSONValue.cpp index 1990a13b6..a229666a9 100644 --- a/src/mqtt/JSONValue.cpp +++ b/src/mqtt/JSONValue.cpp @@ -363,6 +363,19 @@ JSONValue::JSONValue(int m_integer_value) number_value = (double)m_integer_value; } +/** + * Basic constructor for creating a JSON Value of type Number + * + * @access public + * + * @param uint m_integer_value The number to use as the value + */ +JSONValue::JSONValue(uint m_integer_value) +{ + type = JSONType_Number; + number_value = (double)m_integer_value; +} + /** * Basic constructor for creating a JSON Value of type Array * @@ -874,4 +887,4 @@ std::string JSONValue::Indent(size_t depth) depth ? --depth : 0; std::string indentStr(depth * indent_step, ' '); return indentStr; -} +} \ No newline at end of file diff --git a/src/mqtt/JSONValue.h b/src/mqtt/JSONValue.h index 9b231e93f..3a50a831a 100644 --- a/src/mqtt/JSONValue.h +++ b/src/mqtt/JSONValue.h @@ -45,6 +45,7 @@ class JSONValue JSONValue(bool m_bool_value); JSONValue(double m_number_value); JSONValue(int m_integer_value); + JSONValue(uint m_integer_value); JSONValue(const JSONArray &m_array_value); JSONValue(const JSONObject &m_object_value); JSONValue(const JSONValue &m_source); @@ -91,4 +92,4 @@ class JSONValue }; }; -#endif +#endif \ No newline at end of file diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index a7a6b662b..4e5a84477 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -541,7 +541,7 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) { decoded = &scratch; if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) { - msgPayload["battery_level"] = new JSONValue((int)decoded->variant.device_metrics.battery_level); + msgPayload["battery_level"] = new JSONValue((uint)decoded->variant.device_metrics.battery_level); msgPayload["voltage"] = new JSONValue(decoded->variant.device_metrics.voltage); msgPayload["channel_utilization"] = new JSONValue(decoded->variant.device_metrics.channel_utilization); msgPayload["air_util_tx"] = new JSONValue(decoded->variant.device_metrics.air_util_tx); @@ -588,10 +588,10 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) { decoded = &scratch; if ((int)decoded->time) { - msgPayload["time"] = new JSONValue((int)decoded->time); + msgPayload["time"] = new JSONValue((uint)decoded->time); } if ((int)decoded->timestamp) { - msgPayload["timestamp"] = new JSONValue((int)decoded->timestamp); + msgPayload["timestamp"] = new JSONValue((uint)decoded->timestamp); } msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i); msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); @@ -599,13 +599,13 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) msgPayload["altitude"] = new JSONValue((int)decoded->altitude); } if ((int)decoded->ground_speed) { - msgPayload["ground_speed"] = new JSONValue((int)decoded->ground_speed); + msgPayload["ground_speed"] = new JSONValue((uint)decoded->ground_speed); } if (int(decoded->ground_track)) { - msgPayload["ground_track"] = new JSONValue((int)decoded->ground_track); + msgPayload["ground_track"] = new JSONValue((uint)decoded->ground_track); } if (int(decoded->sats_in_view)) { - msgPayload["sats_in_view"] = new JSONValue((int)decoded->sats_in_view); + msgPayload["sats_in_view"] = new JSONValue((uint)decoded->sats_in_view); } if ((int)decoded->PDOP) { msgPayload["PDOP"] = new JSONValue((int)decoded->PDOP); @@ -632,11 +632,11 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) memset(&scratch, 0, sizeof(scratch)); if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) { decoded = &scratch; - msgPayload["id"] = new JSONValue((int)decoded->id); + msgPayload["id"] = new JSONValue((uint)decoded->id); msgPayload["name"] = new JSONValue(decoded->name); msgPayload["description"] = new JSONValue(decoded->description); - msgPayload["expire"] = new JSONValue((int)decoded->expire); - msgPayload["locked_to"] = new JSONValue((int)decoded->locked_to); + msgPayload["expire"] = new JSONValue((uint)decoded->expire); + msgPayload["locked_to"] = new JSONValue((uint)decoded->locked_to); msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i); msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i); jsonObj["payload"] = new JSONValue(msgPayload); @@ -646,16 +646,34 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) }; break; } + case meshtastic_PortNum_NEIGHBORINFO_APP: { + msgType = "neighborinfo"; + meshtastic_NeighborInfo scratch; + meshtastic_NeighborInfo *decoded = NULL; + if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg, + &scratch)) { + decoded = &scratch; + msgPayload["node_id"] = new JSONValue((uint)decoded->node_id); + msgPayload["neighbors_count"] = new JSONValue(decoded->neighbors_count); + msgPayload["neighbors"] = new JSONValue(decoded->neighbors); + } else { + LOG_ERROR("Error decoding protobuf for neighborinfo message!\n"); + } + }; + break; + } // add more packet types here if needed default: break; } - jsonObj["id"] = new JSONValue((int)mp->id); - jsonObj["timestamp"] = new JSONValue((int)mp->rx_time); - jsonObj["to"] = new JSONValue((int)mp->to); - jsonObj["from"] = new JSONValue((int)mp->from); - jsonObj["channel"] = new JSONValue((int)mp->channel); + jsonObj["id"] = new JSONValue((uint)mp->id); + jsonObj["timestamp"] = new JSONValue((uint)mp->rx_time); + jsonObj["to"] = new JSONValue((uint)mp->to); + jsonObj["from"] = new JSONValue((uint)mp->from); + jsonObj["channel"] = new JSONValue((uint)mp->channel); jsonObj["type"] = new JSONValue(msgType.c_str()); jsonObj["sender"] = new JSONValue(owner.id);