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 a281c540b..c5605a281 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 @@ -133,6 +134,8 @@ bool NodeDB::factoryReset() installDefaultChannels(); // third, write everything to disk saveToDisk(); + // write NeighbourInfo + neighborInfoModule->saveProtoForModule(); #ifdef ARCH_ESP32 // This will erase what's in NVS including ssl keys, persistent variables and ble pairing nvs_flash_erase(); @@ -283,6 +286,7 @@ void NodeDB::resetNodes() devicestate.node_db_lite_count = 0; memset(devicestate.node_db_lite, 0, sizeof(devicestate.node_db_lite)); saveDeviceStateToDisk(); + neighborInfoModule->resetNeighbors(); } void NodeDB::installDefaultDeviceState() diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index be76f62a5..afe26f5a3 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); @@ -663,4 +668,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 61de2c265..41a9143ab 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -5,6 +5,7 @@ #include "input/cardKbI2cImpl.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" @@ -46,6 +47,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. @@ -92,4 +94,4 @@ void setupModules() // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra // acks routingModule = new RoutingModule(); -} +} \ No newline at end of file diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp new file mode 100644 index 000000000..a485fe5f8 --- /dev/null +++ b/src/modules/NeighborInfoModule.cpp @@ -0,0 +1,266 @@ +#include "NeighborInfoModule.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "RTC.h" + +#define MAX_NEIGHBOR_AGE 10 * 60 * 1000 // 10 minutes +#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=%d\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 num_neighbors = getNumNeighbors(); + int my_node_id = nodeDB.getNodeNum(); + neighborInfo->node_id = my_node_id; + neighborInfo->last_sent_by_id = my_node_id; + + 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; + neighborInfo->neighbors_count++; + } + } + printNodeDBNodes("DBSTATE"); + printNodeDBNeighbors("NEIGHBORS"); + printNodeDBSelection("COLLECTED", neighborInfo); + return neighborInfo->neighbors_count; +} + +/* 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("RECIEVED", 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() +{ + 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(np->last_sent_by_id, mp.rx_snr); + } +} + +meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum n, 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; + 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; + 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..923ac1f7e --- /dev/null +++ b/src/modules/NeighborInfoModule.h @@ -0,0 +1,78 @@ +#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); + + /* Allocate a new NeighborInfo packet */ + meshtastic_NeighborInfo *allocateNeighborInfoPacket(); + + /// Find a neighbor in our DB, create an empty neighbor if missing + meshtastic_Neighbor *getOrCreateNeighbor(NodeNum n, 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/MQTT.cpp b/src/mqtt/MQTT.cpp index bbdb65c87..fb0bcf14a 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -637,6 +637,24 @@ 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((int)decoded->node_id); + msgPayload["neighbors_count"] = new JSONValue((int)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;