Files
firmware/src/modules/NeighborInfoModule.cpp

256 lines
10 KiB
C++
Raw Normal View History

#include "NeighborInfoModule.h"
#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include <Throttle.h>
NeighborInfoModule *neighborInfoModule;
/*
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 0x%x to Node 0x%x (last sent by 0x%x)", header, np->node_id, nodeDB->getNodeNum(),
np->last_sent_by_id);
LOG_DEBUG("Packet contains %d neighbors", np->neighbors_count);
for (int i = 0; i < np->neighbors_count; i++) {
LOG_DEBUG("Neighbor %d: node_id=0x%x, snr=%.2f", i, np->neighbors[i].node_id, np->neighbors[i].snr);
}
}
/*
Prints the nodeDB neighbors
NOTE: for debugging only
*/
void NeighborInfoModule::printNodeDBNeighbors()
{
LOG_DEBUG("Our NodeDB contains %d neighbors", neighbors.size());
for (size_t i = 0; i < neighbors.size(); i++) {
LOG_DEBUG("Node %d: node_id=0x%x, snr=%.2f", i, neighbors[i].node_id, neighbors[i].snr);
}
}
/* Send our initial owner announcement 35 seconds after we start (to give
* network time to setup) */
NeighborInfoModule::NeighborInfoModule()
2023-08-16 03:17:15 +02:00
: ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg),
concurrency::OSThread("NeighborInfo")
{
ourPortNum = meshtastic_PortNum_NEIGHBORINFO_APP;
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
if (moduleConfig.neighbor_info.enabled) {
isPromiscuous = true; // Update neighbors from all packets
setIntervalFromNow(Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval,
default_telemetry_broadcast_interval_secs));
} else {
LOG_DEBUG("NeighborInfoModule is disabled");
disable();
}
}
/*
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)
{
NodeNum my_node_id = nodeDB->getNodeNum();
neighborInfo->node_id = my_node_id;
neighborInfo->last_sent_by_id = my_node_id;
neighborInfo->node_broadcast_interval_secs =
Default::getConfiguredOrDefault(moduleConfig.neighbor_info.update_interval, default_telemetry_broadcast_interval_secs);
cleanUpNeighbors();
for (auto nbr : neighbors) {
if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (nbr.node_id != my_node_id)) {
neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id;
neighborInfo->neighbors[neighborInfo->neighbors_count].snr = nbr.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++;
}
}
printNodeDBNeighbors();
return neighborInfo->neighbors_count;
}
/*
Remove neighbors from the database that we haven't heard from in a while
*/
void NeighborInfoModule::cleanUpNeighbors()
{
uint32_t now = getTime();
NodeNum my_node_id = nodeDB->getNodeNum();
for (auto it = neighbors.rbegin(); it != neighbors.rend();) {
// We will remove a neighbor if we haven't heard from them in twice the
// broadcast interval cannot use isWithinTimespanMs() as it->last_rx_time is
// seconds since 1970
if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) {
LOG_DEBUG("Remove neighbor with node ID 0x%x", it->node_id);
it = std::vector<meshtastic_Neighbor>::reverse_iterator(
neighbors.erase(std::next(it).base())); // Erase the element and update the iterator
} else {
++it;
}
}
}
/* Send neighbor info to the mesh */
void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies)
{
meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero;
collectNeighborInfo(&neighborInfo);
Backmerge (#7782) * Merge pull request #7777 from meshtastic/create-pull-request/bump-version Bump release version * Only send Neighbours if we have some to send. (#7493) * Only send Neighbours if we have some to send. The original intent of NeighborInfo was that when a NeighbourInfo was sent all of the nodes that saw it would reply with NeighbourInfo. So, NeighbourInfo was sent even if there were no hop-zero nodes in the NodeDB. Since 2023, when this was implemented, our understanding of running city-wide meshes has improved substantially. We have taken steps to reduce the impact of NeighborInfo over LoRa. This change aligns with those ideas: we will now only send NeighborInfo if we have some neighbors to contribute. The impact of this change is that a node must first see another directly connected node in another packet type before NeighborInfo is sent. This means that a node with no neighbors is no longer able to trigger other nodes to broadcast NeighborInfo. It will, however, receive the regular periodic broadcast of NeighborInfo, and will be able to send NeighborInfo if it has at least 1 neighbor. * Include all the things * AvOid memleak * We don't gotTime if time is 2019. (#7772) There are certain GPS chips that have a hard-coded time in firmware that they will return before lock. We set our own hard-coded time, BUILD_EPOCH, that should be newer and use the comparison to not set a bad time. In https://github.com/meshtastic/firmware/pull/7261 we introduced the RTCSetResult and improved it in https://github.com/meshtastic/firmware/pull/7375 . However, the original try-fix left logic in GPS.cpp that could still result in broadcasting the bad time. Further, as part of our fix we cleared the GPS buffer if we didn't get a good time. The mesh was hurting at the time, so this was a reasonable approach. However, given time tends to come in when we're trying to get early lock, this had the potential side effect of throwing away valuable information to get position lock. This change reverses the clearBuffer and changes the logic so if time is not set it will not be broadcast. Fixes https://github.com/meshtastic/firmware/issues/7771 Fixes https://github.com/meshtastic/firmware/issues/7750 --------- Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-08-29 10:31:55 -05:00
// only send neighbours if we have some to send
if (neighborInfo.neighbors_count > 0) {
meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo);
p->to = dest;
p->decoded.want_response = wantReplies;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
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()
{
if (moduleConfig.neighbor_info.transmit_over_lora &&
(!channels.isDefaultChannel(channels.getPrimaryIndex()) || !RadioInterface::uses_default_frequency_slot) &&
airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) {
sendNeighborInfo(NODENUM_BROADCAST, false);
} else {
sendNeighborInfo(NODENUM_BROADCAST_NO_LORA, false);
}
2024-08-19 07:04:48 -05:00
return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs);
}
meshtastic_MeshPacket *NeighborInfoModule::allocReply()
{
LOG_INFO("NeighborInfoRequested.");
if (lastSentReply && Throttle::isWithinTimespanMs(lastSentReply, 3 * 60 * 1000)) {
LOG_DEBUG("Skip Neighbors reply since we sent a reply <3min ago");
ignoreRequest = true; // Mark it as ignored for MeshModule
return nullptr;
}
meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero;
collectNeighborInfo(&neighborInfo);
meshtastic_MeshPacket *reply = allocDataProtobuf(neighborInfo);
if (reply) {
lastSentReply = millis(); // Track when we sent this reply
}
return reply;
}
/*
Collect a received 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)
{
LOG_DEBUG("NeighborInfo: handleReceivedProtobuf");
if (np) {
printNeighborInfo("RECEIVED", np);
// Ignore dummy/interceptable packets: single neighbor with nodeId 0 and snr 0
if (np->neighbors_count != 1 || np->neighbors[0].node_id != 0 || np->neighbors[0].snr != 0.0f) {
LOG_DEBUG(" Updating neighbours");
updateNeighbors(mp, np);
} else {
LOG_DEBUG(" Ignoring dummy neighbor info packet (single neighbor with nodeId 0, snr 0)");
}
} else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) {
LOG_DEBUG("Get or create neighbor: %u with snr %f", mp.from, mp.rx_snr);
// If the hopLimit is the same as hopStart, then it is a neighbor
getOrCreateNeighbor(mp.from, mp.from, 0,
mp.rx_snr); // Set the broadcast interval to 0, as we don't know it
}
// 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::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n)
{
n->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, n);
}
void NeighborInfoModule::resetNeighbors()
{
neighbors.clear();
}
void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np)
{
LOG_DEBUG("updateNeighbors");
// 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, float 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 (size_t i = 0; i < neighbors.size(); i++) {
if (neighbors[i].node_id == n) {
// if found, update it
neighbors[i].snr = snr;
neighbors[i].last_rx_time = getTime();
// Only if this is the original sender, the broadcast interval corresponds
// to it
if (originalSender == n && node_broadcast_interval_secs != 0)
neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs;
return &neighbors[i];
}
}
// otherwise, allocate one and assign data to it
meshtastic_Neighbor new_nbr = meshtastic_Neighbor_init_zero;
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 && node_broadcast_interval_secs != 0)
new_nbr.node_broadcast_interval_secs = node_broadcast_interval_secs;
else // Assume the same broadcast interval as us for the neighbor if we don't
// know it
new_nbr.node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval;
if (neighbors.size() < MAX_NUM_NEIGHBORS) {
neighbors.push_back(new_nbr);
} else {
// If we have too many neighbors, replace the oldest one
LOG_WARN("Neighbor DB is full, replace oldest neighbor");
neighbors.erase(neighbors.begin());
neighbors.push_back(new_nbr);
}
return &neighbors.back();
Backmerge (#7782) * Merge pull request #7777 from meshtastic/create-pull-request/bump-version Bump release version * Only send Neighbours if we have some to send. (#7493) * Only send Neighbours if we have some to send. The original intent of NeighborInfo was that when a NeighbourInfo was sent all of the nodes that saw it would reply with NeighbourInfo. So, NeighbourInfo was sent even if there were no hop-zero nodes in the NodeDB. Since 2023, when this was implemented, our understanding of running city-wide meshes has improved substantially. We have taken steps to reduce the impact of NeighborInfo over LoRa. This change aligns with those ideas: we will now only send NeighborInfo if we have some neighbors to contribute. The impact of this change is that a node must first see another directly connected node in another packet type before NeighborInfo is sent. This means that a node with no neighbors is no longer able to trigger other nodes to broadcast NeighborInfo. It will, however, receive the regular periodic broadcast of NeighborInfo, and will be able to send NeighborInfo if it has at least 1 neighbor. * Include all the things * AvOid memleak * We don't gotTime if time is 2019. (#7772) There are certain GPS chips that have a hard-coded time in firmware that they will return before lock. We set our own hard-coded time, BUILD_EPOCH, that should be newer and use the comparison to not set a bad time. In https://github.com/meshtastic/firmware/pull/7261 we introduced the RTCSetResult and improved it in https://github.com/meshtastic/firmware/pull/7375 . However, the original try-fix left logic in GPS.cpp that could still result in broadcasting the bad time. Further, as part of our fix we cleared the GPS buffer if we didn't get a good time. The mesh was hurting at the time, so this was a reasonable approach. However, given time tends to come in when we're trying to get early lock, this had the potential side effect of throwing away valuable information to get position lock. This change reverses the clearBuffer and changes the logic so if time is not set it will not be broadcast. Fixes https://github.com/meshtastic/firmware/issues/7771 Fixes https://github.com/meshtastic/firmware/issues/7750 --------- Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-08-29 10:31:55 -05:00
}