mirror of
https://github.com/agessaman/MeshCore.git
synced 2026-08-29 07:38:43 +00:00
feat(mqtt): include RSSI in neighbor data for improved metrics
This commit is contained in:
Submodule
+1
Submodule .wt-station-g3-prod added at bfc43e94f8
@@ -710,7 +710,7 @@ Full packet data with RF characteristics and metadata.
|
||||
Minimal raw packet data for map integration.
|
||||
|
||||
### Neighbors Topic: `meshcore/{IATA}/{DEVICE_PUBLIC_KEY}/neighbors`
|
||||
Cached zero-hop repeater neighbors with SNR, last-heard age, and flood-allowed scopes. Published on `discover.scopes` or periodically when `mqtt.neighbors` is enabled (observer builds with neighbors compiled in; non-PSRAM builds cap the table at 20 entries and set `truncated`). Goes to every configured slot's `neighbors` topic at QoS 0, retained only where the preset allows it.
|
||||
Cached zero-hop repeater neighbors with SNR, RSSI, last-heard age, and flood-allowed scopes. Published on `discover.scopes` or periodically when `mqtt.neighbors` is enabled (observer builds with neighbors compiled in; non-PSRAM builds cap the table at 20 entries and set `truncated`). Goes to every configured slot's `neighbors` topic at QoS 0, retained only where the preset allows it.
|
||||
|
||||
Periodic publishing first runs a 60-second zero-hop neighbor refresh equivalent to `discover.neighbors`, then queries the refreshed table for scopes and publishes when the scope-query phase completes.
|
||||
|
||||
@@ -810,6 +810,7 @@ While `mqtt.neighbors` is on, `get mqtt.status` appends `nbr: <next>/<last>` —
|
||||
{
|
||||
"pubkey": "0011223344556677...",
|
||||
"snr": 9.75,
|
||||
"rssi": -87,
|
||||
"heard_secs_ago": 42,
|
||||
"scopes": "DEN,APRS",
|
||||
"status": "responded"
|
||||
@@ -817,6 +818,7 @@ While `mqtt.neighbors` is on, `get mqtt.status` appends `nbr: <next>/<last>` —
|
||||
{
|
||||
"pubkey": "8899AABBCCDDEEFF...",
|
||||
"snr": 12.5,
|
||||
"rssi": -95,
|
||||
"heard_secs_ago": null,
|
||||
"scopes": "DEN",
|
||||
"status": "responded"
|
||||
|
||||
@@ -786,8 +786,8 @@ void MyMesh::onControlDataRecv(mesh::Packet *packet) {
|
||||
}
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_CONTROL_DATA;
|
||||
out_frame[i++] = (int8_t)(_radio->getLastSNR() * 4);
|
||||
out_frame[i++] = (int8_t)(_radio->getLastRSSI());
|
||||
out_frame[i++] = (int8_t)(packet->getSNR() * 4);
|
||||
out_frame[i++] = (int8_t)packet->getRSSI();
|
||||
out_frame[i++] = packet->path_len;
|
||||
memcpy(&out_frame[i], packet->payload, packet->payload_len);
|
||||
i += packet->payload_len;
|
||||
@@ -806,8 +806,8 @@ void MyMesh::onRawDataRecv(mesh::Packet *packet) {
|
||||
}
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_RAW_DATA;
|
||||
out_frame[i++] = (int8_t)(_radio->getLastSNR() * 4);
|
||||
out_frame[i++] = (int8_t)(_radio->getLastRSSI());
|
||||
out_frame[i++] = (int8_t)(packet->getSNR() * 4);
|
||||
out_frame[i++] = (int8_t)packet->getRSSI();
|
||||
out_frame[i++] = 0xFF; // reserved (possibly path_len in future)
|
||||
memcpy(&out_frame[i], packet->payload, packet->payload_len);
|
||||
i += packet->payload_len;
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
#define LAZY_CONTACTS_WRITE_DELAY 5000
|
||||
|
||||
void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr) {
|
||||
void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr, int16_t rssi) {
|
||||
#if MAX_NEIGHBOURS // check if neighbours enabled
|
||||
// find existing neighbour, else use least recently updated
|
||||
uint32_t oldest_timestamp = 0xFFFFFFFF;
|
||||
@@ -95,6 +95,7 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn
|
||||
neighbour->advert_timestamp = timestamp;
|
||||
neighbour->heard_timestamp = getRTCClock()->getCurrentTime();
|
||||
neighbour->snr = (int8_t)(snr * 4);
|
||||
neighbour->rssi = rssi;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -756,7 +757,7 @@ void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32
|
||||
if (packet->getPathHashCount() == 0 && !isShare(packet)) {
|
||||
AdvertDataParser parser(app_data, app_data_len);
|
||||
if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters
|
||||
putNeighbour(id, timestamp, packet->getSNR());
|
||||
putNeighbour(id, timestamp, packet->getSNR(), packet->getRSSI());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -770,7 +771,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
if (neighbor_discover_active && i >= NEIGHBOR_DISCOVER_PEER_BASE) {
|
||||
int oi = i - NEIGHBOR_DISCOVER_PEER_BASE;
|
||||
if (type == PAYLOAD_TYPE_RESPONSE && oi >= 0 && oi < neighbor_discover_count) {
|
||||
handleNeighborDiscoverResponse(oi, data, len);
|
||||
handleNeighborDiscoverResponse(oi, data, len, packet->getSNR(), packet->getRSSI());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -786,7 +787,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
if (neighbor_discover_active && type == PAYLOAD_TYPE_RESPONSE) {
|
||||
for (int oi = 0; oi < neighbor_discover_count; oi++) {
|
||||
if (client->id.matches(neighbor_discover[oi].id)
|
||||
&& handleNeighborDiscoverResponse(oi, data, len)) {
|
||||
&& handleNeighborDiscoverResponse(oi, data, len, packet->getSNR(), packet->getRSSI())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -960,7 +961,7 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
||||
if (id.matches(self_id)) {
|
||||
return;
|
||||
}
|
||||
putNeighbour(id, rtc_clock.getCurrentTime(), packet->getSNR());
|
||||
putNeighbour(id, rtc_clock.getCurrentTime(), packet->getSNR(), packet->getRSSI());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1932,6 +1933,7 @@ bool MyMesh::completeNeighborDiscoverEntry() {
|
||||
MQTTMessageBuilder::NeighborsMessageEntry measured = {
|
||||
pubkey_hex,
|
||||
entry.snr / 4.0f,
|
||||
entry.rssi,
|
||||
UINT32_MAX,
|
||||
entry.scopes,
|
||||
entry.status == ND_RESPONDED ? "responded"
|
||||
@@ -1955,7 +1957,8 @@ bool MyMesh::completeNeighborDiscoverEntry() {
|
||||
|
||||
// Match a RESPONSE against the pending overlay entry by tag; copy its scope
|
||||
// string (payload after the 8-byte {tag}{clock} header) into the entry.
|
||||
bool MyMesh::handleNeighborDiscoverResponse(int overlay_idx, const uint8_t* data, size_t len) {
|
||||
bool MyMesh::handleNeighborDiscoverResponse(int overlay_idx, const uint8_t* data, size_t len,
|
||||
float snr, int16_t rssi) {
|
||||
if (overlay_idx < 0 || overlay_idx >= neighbor_discover_count) return false;
|
||||
NeighborDiscoverEntry& entry = neighbor_discover[overlay_idx];
|
||||
if (entry.status != ND_PENDING || len < 8) return false;
|
||||
@@ -1972,19 +1975,24 @@ bool MyMesh::handleNeighborDiscoverResponse(int overlay_idx, const uint8_t* data
|
||||
entry.scopes[scope_len] = 0;
|
||||
entry.status = ND_RESPONDED;
|
||||
// A zero-hop reply is proof we heard this neighbour now, so re-stamp both the
|
||||
// snapshot and the live table; a stamp taken before time sync heals here.
|
||||
// snapshot and the live table with this packet's RX metrics; a stamp taken
|
||||
// before time sync heals here.
|
||||
entry.heard_timestamp = getRTCClock()->getCurrentTime();
|
||||
touchNeighbourHeard(entry.id, entry.heard_timestamp);
|
||||
entry.snr = (int8_t)(snr * 4);
|
||||
entry.rssi = rssi;
|
||||
touchNeighbourHeard(entry.id, entry.heard_timestamp, snr, rssi);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Refresh a live neighbour's heard time only: a scope reply carries no advert
|
||||
// timestamp or SNR to update.
|
||||
void MyMesh::touchNeighbourHeard(const mesh::Identity& id, uint32_t heard_timestamp) {
|
||||
// Refresh a live neighbour's heard time and RX metrics from a scope reply.
|
||||
void MyMesh::touchNeighbourHeard(const mesh::Identity& id, uint32_t heard_timestamp,
|
||||
float snr, int16_t rssi) {
|
||||
#if MAX_NEIGHBOURS
|
||||
for (int i = 0; i < MAX_NEIGHBOURS; i++) {
|
||||
if (id.matches(neighbours[i].id)) {
|
||||
neighbours[i].heard_timestamp = heard_timestamp;
|
||||
neighbours[i].snr = (int8_t)(snr * 4);
|
||||
neighbours[i].rssi = rssi;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2107,6 +2115,7 @@ void MyMesh::finishNeighborDiscover() {
|
||||
mesh::Utils::toHex(hex, entry.id.pub_key, PUB_KEY_SIZE);
|
||||
entries[i].pubkey_hex = hex;
|
||||
entries[i].snr = entry.snr / 4.0f;
|
||||
entries[i].rssi = entry.rssi;
|
||||
bool heard_known = neighborHeardAgeUsable(entry.heard_timestamp, now_secs);
|
||||
entries[i].heard_unknown = !heard_known;
|
||||
entries[i].heard_secs_ago = heard_known ? (now_secs - entry.heard_timestamp) : 0;
|
||||
@@ -2250,6 +2259,7 @@ bool MyMesh::startNeighborDiscover(char* reply) {
|
||||
entry.id = neighbours[i].id;
|
||||
entry.heard_timestamp = neighbours[i].heard_timestamp;
|
||||
entry.snr = neighbours[i].snr;
|
||||
entry.rssi = neighbours[i].rssi;
|
||||
entry.scopes[0] = 0;
|
||||
entry.tag = 0;
|
||||
entry.status = ND_UNSENT;
|
||||
|
||||
@@ -76,6 +76,7 @@ struct NeighbourInfo {
|
||||
uint32_t advert_timestamp;
|
||||
uint32_t heard_timestamp;
|
||||
int8_t snr; // multiplied by 4, user should divide to get float value
|
||||
int16_t rssi; // dBm from last heard packet
|
||||
};
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
@@ -164,6 +165,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
mesh::Identity id; // immutable snapshot: neighbour table can change mid-pass
|
||||
uint32_t heard_timestamp;
|
||||
int8_t snr; // multiplied by 4
|
||||
int16_t rssi; // dBm from last heard packet
|
||||
uint32_t tag; // anon-regions request tag we're waiting on
|
||||
char scopes[96]; // scope names from the response
|
||||
uint8_t status; // NeighborDiscoverStatus
|
||||
@@ -194,8 +196,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
bool startNeighborDiscover(char* reply);
|
||||
void loopNeighborDiscover();
|
||||
void finishNeighborDiscover();
|
||||
bool handleNeighborDiscoverResponse(int overlay_idx, const uint8_t* data, size_t len);
|
||||
void touchNeighbourHeard(const mesh::Identity& id, uint32_t heard_timestamp);
|
||||
bool handleNeighborDiscoverResponse(int overlay_idx, const uint8_t* data, size_t len,
|
||||
float snr, int16_t rssi);
|
||||
void touchNeighbourHeard(const mesh::Identity& id, uint32_t heard_timestamp,
|
||||
float snr, int16_t rssi);
|
||||
void getLocalScopes(char* buf, size_t len);
|
||||
// Overlay peer indices are offset by this base so onPeerDataRecv can tell a
|
||||
// discovery response apart from a normal ACL-client index.
|
||||
@@ -204,7 +208,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
static const int NEIGHBOR_DISCOVER_MIN_FREE_PACKETS = 5;
|
||||
#endif
|
||||
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr, int16_t rssi);
|
||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
|
||||
uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
|
||||
uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
|
||||
|
||||
@@ -518,7 +518,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
if (neighbor_discover_active && i >= NEIGHBOR_DISCOVER_PEER_BASE) {
|
||||
int oi = i - NEIGHBOR_DISCOVER_PEER_BASE;
|
||||
if (type == PAYLOAD_TYPE_RESPONSE && oi >= 0 && oi < neighbor_discover_count) {
|
||||
handleNeighborDiscoverResponse(oi, data, len);
|
||||
handleNeighborDiscoverResponse(oi, data, len, packet->getSNR(), packet->getRSSI());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -534,7 +534,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
if (neighbor_discover_active && type == PAYLOAD_TYPE_RESPONSE) {
|
||||
for (int oi = 0; oi < neighbor_discover_count; oi++) {
|
||||
if (client->id.matches(neighbor_discover[oi].id)
|
||||
&& handleNeighborDiscoverResponse(oi, data, len)) {
|
||||
&& handleNeighborDiscoverResponse(oi, data, len, packet->getSNR(), packet->getRSSI())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -734,7 +734,7 @@ void MyMesh::onAckRecv(mesh::Packet *packet, uint32_t ack_crc) {
|
||||
#define CTL_TYPE_NODE_DISCOVER_REQ 0x80
|
||||
#define CTL_TYPE_NODE_DISCOVER_RESP 0x90
|
||||
|
||||
void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr) {
|
||||
void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr, int16_t rssi) {
|
||||
// find existing neighbour, else use least recently updated
|
||||
uint32_t oldest_timestamp = 0xFFFFFFFF;
|
||||
NeighbourInfo *neighbour = &neighbours[0];
|
||||
@@ -757,6 +757,7 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn
|
||||
neighbour->advert_timestamp = timestamp;
|
||||
neighbour->heard_timestamp = getRTCClock()->getCurrentTime();
|
||||
neighbour->snr = (int8_t)(snr * 4);
|
||||
neighbour->rssi = rssi;
|
||||
}
|
||||
|
||||
static bool isShare(const mesh::Packet *packet) {
|
||||
@@ -774,7 +775,7 @@ void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32
|
||||
if (packet->getPathHashCount() == 0 && !isShare(packet)) {
|
||||
AdvertDataParser parser(app_data, app_data_len);
|
||||
if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters
|
||||
putNeighbour(id, timestamp, packet->getSNR());
|
||||
putNeighbour(id, timestamp, packet->getSNR(), packet->getRSSI());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -808,7 +809,7 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
||||
if (id.matches(self_id)) {
|
||||
return;
|
||||
}
|
||||
putNeighbour(id, getRTCClock()->getCurrentTime(), packet->getSNR());
|
||||
putNeighbour(id, getRTCClock()->getCurrentTime(), packet->getSNR(), packet->getRSSI());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1818,6 +1819,7 @@ bool MyMesh::completeNeighborDiscoverEntry() {
|
||||
MQTTMessageBuilder::NeighborsMessageEntry measured = {
|
||||
pubkey_hex,
|
||||
entry.snr / 4.0f,
|
||||
entry.rssi,
|
||||
UINT32_MAX,
|
||||
entry.scopes,
|
||||
entry.status == ND_RESPONDED ? "responded"
|
||||
@@ -1841,7 +1843,8 @@ bool MyMesh::completeNeighborDiscoverEntry() {
|
||||
|
||||
// Match a RESPONSE against the pending overlay entry by tag; copy its scope
|
||||
// string (payload after the 8-byte {tag}{clock} header) into the entry.
|
||||
bool MyMesh::handleNeighborDiscoverResponse(int overlay_idx, const uint8_t* data, size_t len) {
|
||||
bool MyMesh::handleNeighborDiscoverResponse(int overlay_idx, const uint8_t* data, size_t len,
|
||||
float snr, int16_t rssi) {
|
||||
if (overlay_idx < 0 || overlay_idx >= neighbor_discover_count) return false;
|
||||
NeighborDiscoverEntry& entry = neighbor_discover[overlay_idx];
|
||||
if (entry.status != ND_PENDING || len < 8) return false;
|
||||
@@ -1858,18 +1861,23 @@ bool MyMesh::handleNeighborDiscoverResponse(int overlay_idx, const uint8_t* data
|
||||
entry.scopes[scope_len] = 0;
|
||||
entry.status = ND_RESPONDED;
|
||||
// A zero-hop reply is proof we heard this neighbour now, so re-stamp both the
|
||||
// snapshot and the live table; a stamp taken before time sync heals here.
|
||||
// snapshot and the live table with this packet's RX metrics; a stamp taken
|
||||
// before time sync heals here.
|
||||
entry.heard_timestamp = getRTCClock()->getCurrentTime();
|
||||
touchNeighbourHeard(entry.id, entry.heard_timestamp);
|
||||
entry.snr = (int8_t)(snr * 4);
|
||||
entry.rssi = rssi;
|
||||
touchNeighbourHeard(entry.id, entry.heard_timestamp, snr, rssi);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Refresh a live neighbour's heard time only: a scope reply carries no advert
|
||||
// timestamp or SNR to update.
|
||||
void MyMesh::touchNeighbourHeard(const mesh::Identity& id, uint32_t heard_timestamp) {
|
||||
// Refresh a live neighbour's heard time and RX metrics from a scope reply.
|
||||
void MyMesh::touchNeighbourHeard(const mesh::Identity& id, uint32_t heard_timestamp,
|
||||
float snr, int16_t rssi) {
|
||||
for (int i = 0; i < MAX_NEIGHBOURS; i++) {
|
||||
if (id.matches(neighbours[i].id)) {
|
||||
neighbours[i].heard_timestamp = heard_timestamp;
|
||||
neighbours[i].snr = (int8_t)(snr * 4);
|
||||
neighbours[i].rssi = rssi;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1991,6 +1999,7 @@ void MyMesh::finishNeighborDiscover() {
|
||||
mesh::Utils::toHex(hex, entry.id.pub_key, PUB_KEY_SIZE);
|
||||
entries[i].pubkey_hex = hex;
|
||||
entries[i].snr = entry.snr / 4.0f;
|
||||
entries[i].rssi = entry.rssi;
|
||||
bool heard_known = neighborHeardAgeUsable(entry.heard_timestamp, now_secs);
|
||||
entries[i].heard_unknown = !heard_known;
|
||||
entries[i].heard_secs_ago = heard_known ? (now_secs - entry.heard_timestamp) : 0;
|
||||
@@ -2134,6 +2143,7 @@ bool MyMesh::startNeighborDiscover(char* reply) {
|
||||
entry.id = neighbours[i].id;
|
||||
entry.heard_timestamp = neighbours[i].heard_timestamp;
|
||||
entry.snr = neighbours[i].snr;
|
||||
entry.rssi = neighbours[i].rssi;
|
||||
entry.scopes[0] = 0;
|
||||
entry.tag = 0;
|
||||
entry.status = ND_UNSENT;
|
||||
|
||||
@@ -104,6 +104,7 @@ struct NeighbourInfo {
|
||||
uint32_t advert_timestamp;
|
||||
uint32_t heard_timestamp;
|
||||
int8_t snr; // multiplied by 4, user should divide to get float value
|
||||
int16_t rssi; // dBm from last heard packet
|
||||
};
|
||||
|
||||
class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
@@ -156,6 +157,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
mesh::Identity id;
|
||||
uint32_t heard_timestamp;
|
||||
int8_t snr;
|
||||
int16_t rssi;
|
||||
uint32_t tag;
|
||||
char scopes[96];
|
||||
uint8_t status;
|
||||
@@ -177,7 +179,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
char self_default_scope_buf[31];
|
||||
char neighbor_discover_origin[32];
|
||||
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr, int16_t rssi);
|
||||
void sendNodeDiscoverReq();
|
||||
mesh::Packet* sendAnonRegionsReq(const mesh::Identity& target, uint32_t& tag);
|
||||
bool cancelNeighborDiscoverRequest();
|
||||
@@ -188,8 +190,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
bool startNeighborDiscover(char* reply);
|
||||
void loopNeighborDiscover();
|
||||
void finishNeighborDiscover();
|
||||
bool handleNeighborDiscoverResponse(int overlay_idx, const uint8_t* data, size_t len);
|
||||
void touchNeighbourHeard(const mesh::Identity& id, uint32_t heard_timestamp);
|
||||
bool handleNeighborDiscoverResponse(int overlay_idx, const uint8_t* data, size_t len,
|
||||
float snr, int16_t rssi);
|
||||
void touchNeighbourHeard(const mesh::Identity& id, uint32_t heard_timestamp,
|
||||
float snr, int16_t rssi);
|
||||
void getLocalScopes(char* buf, size_t len);
|
||||
static const int NEIGHBOR_DISCOVER_PEER_BASE = 1000;
|
||||
static const unsigned long NEIGHBOR_DISCOVER_QUEUE_TIMEOUT_MS = 29000;
|
||||
|
||||
+3
-1
@@ -237,6 +237,7 @@ void Dispatcher::checkRecv() {
|
||||
} else {
|
||||
if (tryParsePacket(pkt, raw, len)) {
|
||||
pkt->_snr = _radio->getLastSNR() * 4.0f;
|
||||
pkt->_rssi = (int16_t)_radio->getLastRSSI();
|
||||
score = _radio->packetScore(_radio->getLastSNR(), len);
|
||||
air_time = _radio->getEstAirtimeFor(len);
|
||||
rx_air_time += air_time;
|
||||
@@ -254,7 +255,7 @@ void Dispatcher::checkRecv() {
|
||||
Serial.print(getLogDateTime());
|
||||
Serial.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d time=%d",
|
||||
pkt->getRawLength(), pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len,
|
||||
(int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000), air_time);
|
||||
(int)pkt->getSNR(), (int)pkt->getRSSI(), (int)(score*1000), air_time);
|
||||
|
||||
static uint8_t packet_hash[MAX_HASH_SIZE];
|
||||
pkt->calculatePacketHash(packet_hash);
|
||||
@@ -393,6 +394,7 @@ Packet* Dispatcher::obtainNewPacket() {
|
||||
} else {
|
||||
pkt->payload_len = pkt->path_len = 0;
|
||||
pkt->_snr = 0;
|
||||
pkt->_rssi = 0;
|
||||
}
|
||||
return pkt;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
uint8_t path[MAX_PATH_SIZE];
|
||||
uint8_t payload[MAX_PACKET_PAYLOAD];
|
||||
int8_t _snr;
|
||||
int16_t _rssi; // dBm, frozen at RX with _snr
|
||||
|
||||
/**
|
||||
* \brief calculate the hash of payload + type
|
||||
@@ -90,6 +91,7 @@ public:
|
||||
bool isMarkedDoNotRetransmit() const { return header == 0xFF; }
|
||||
|
||||
float getSNR() const { return ((float)_snr) / 4.0f; }
|
||||
int16_t getRSSI() const { return _rssi; }
|
||||
|
||||
/**
|
||||
* \returns the encoded/wire format length of this packet
|
||||
|
||||
@@ -220,6 +220,7 @@ static void addNeighborsMessageEntry(
|
||||
JsonObject nb = arr.add<JsonObject>();
|
||||
nb["pubkey"] = neighbor.pubkey_hex;
|
||||
nb["snr"] = neighbor.snr;
|
||||
nb["rssi"] = neighbor.rssi;
|
||||
if (neighbor.heard_unknown) {
|
||||
nb["heard_secs_ago"] = nullptr; // age unknown, not zero
|
||||
} else {
|
||||
|
||||
@@ -72,6 +72,7 @@ public:
|
||||
struct NeighborsMessageEntry {
|
||||
const char* pubkey_hex;
|
||||
float snr;
|
||||
int rssi; // dBm from the last heard packet
|
||||
uint32_t heard_secs_ago;
|
||||
const char* scopes;
|
||||
const char* status;
|
||||
|
||||
@@ -235,8 +235,8 @@ TEST(MQTTPayloadBuilder, MaximumRepresentativePacketAndRawPayloadsRemainValid) {
|
||||
|
||||
TEST(MQTTPayloadBuilder, NeighborsMessageRoundTripsSelfAndEntries) {
|
||||
MQTTPayloadBuilder::NeighborsMessageEntry neighbors[] = {
|
||||
{"0011223344556677", 9.75f, 42, "DEN,APRS", "active"},
|
||||
{"8899AABBCCDDEEFF", -3.5f, 3600, "", "stale"},
|
||||
{"0011223344556677", 9.75f, -87, 42, "DEN,APRS", "active"},
|
||||
{"8899AABBCCDDEEFF", -3.5f, -110, 3600, "", "stale"},
|
||||
};
|
||||
|
||||
JsonDocument scratch;
|
||||
@@ -262,18 +262,20 @@ TEST(MQTTPayloadBuilder, NeighborsMessageRoundTripsSelfAndEntries) {
|
||||
ASSERT_EQ(2U, arr.size());
|
||||
EXPECT_STREQ("0011223344556677", arr[0]["pubkey"].as<const char*>());
|
||||
EXPECT_FLOAT_EQ(9.75f, arr[0]["snr"].as<float>());
|
||||
EXPECT_EQ(-87, arr[0]["rssi"].as<int>());
|
||||
EXPECT_EQ(42U, arr[0]["heard_secs_ago"].as<uint32_t>());
|
||||
EXPECT_STREQ("DEN,APRS", arr[0]["scopes"].as<const char*>());
|
||||
EXPECT_STREQ("active", arr[0]["status"].as<const char*>());
|
||||
EXPECT_STREQ("8899AABBCCDDEEFF", arr[1]["pubkey"].as<const char*>());
|
||||
EXPECT_EQ(-110, arr[1]["rssi"].as<int>());
|
||||
EXPECT_STREQ("", arr[1]["scopes"].as<const char*>());
|
||||
EXPECT_STREQ("stale", arr[1]["status"].as<const char*>());
|
||||
}
|
||||
|
||||
TEST(MQTTPayloadBuilder, NeighborsMessageMeasurementsMatchCompletePayload) {
|
||||
MQTTPayloadBuilder::NeighborsMessageEntry neighbors[] = {
|
||||
{"0011223344556677", 9.75f, UINT32_MAX, "DEN,APRS", "responded"},
|
||||
{"8899AABBCCDDEEFF", -3.5f, UINT32_MAX, "", "timeout"},
|
||||
{"0011223344556677", 9.75f, -87, UINT32_MAX, "DEN,APRS", "responded"},
|
||||
{"8899AABBCCDDEEFF", -3.5f, -110, UINT32_MAX, "", "timeout"},
|
||||
};
|
||||
|
||||
size_t measured =
|
||||
@@ -302,7 +304,7 @@ TEST(MQTTPayloadBuilder, NeighborsMessageMeasuredPrefixStopsBeforeOverflow) {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
snprintf(keys[i], sizeof(keys[i]), "%016X", i);
|
||||
neighbors[i] = {
|
||||
keys[i], static_cast<float>(i), UINT32_MAX,
|
||||
keys[i], static_cast<float>(i), -90 - i, UINT32_MAX,
|
||||
long_scopes, "responded"};
|
||||
}
|
||||
|
||||
@@ -345,8 +347,8 @@ TEST(MQTTPayloadBuilder, NeighborsMessageMeasuredPrefixStopsBeforeOverflow) {
|
||||
|
||||
TEST(MQTTPayloadBuilder, NeighborsMessageFallbackMarksTruncated) {
|
||||
MQTTPayloadBuilder::NeighborsMessageEntry neighbors[] = {
|
||||
{"0011223344556677", 9.75f, 1, "DEN", "responded"},
|
||||
{"8899AABBCCDDEEFF", -3.5f, 2, "APRS", "responded"},
|
||||
{"0011223344556677", 9.75f, -87, 1, "DEN", "responded"},
|
||||
{"8899AABBCCDDEEFF", -3.5f, -110, 2, "APRS", "responded"},
|
||||
};
|
||||
size_t first_only_size =
|
||||
MQTTPayloadBuilder::measureNeighborsMessageBase(
|
||||
@@ -369,7 +371,7 @@ TEST(MQTTPayloadBuilder, NeighborsMessageFallbackMarksTruncated) {
|
||||
|
||||
TEST(MQTTPayloadBuilder, NeighborsMessageFailsCleanlyOnAllocationFailure) {
|
||||
MQTTPayloadBuilder::NeighborsMessageEntry neighbor = {
|
||||
"0011223344556677", 9.75f, 1, "DEN", "responded"};
|
||||
"0011223344556677", 9.75f, -87, 1, "DEN", "responded"};
|
||||
RejectAllJsonAllocations allocator;
|
||||
JsonDocument scratch(&allocator);
|
||||
char buffer[512];
|
||||
@@ -406,6 +408,7 @@ TEST(MQTTPayloadBuilder, NeighborsMessageDropsTailWhenBufferFills) {
|
||||
snprintf(keys[i], sizeof(keys[i]), "%016X", i);
|
||||
neighbors[i].pubkey_hex = keys[i];
|
||||
neighbors[i].snr = static_cast<float>(i);
|
||||
neighbors[i].rssi = -90 - i;
|
||||
neighbors[i].heard_secs_ago = static_cast<uint32_t>(i) * 10U;
|
||||
neighbors[i].heard_unknown = false;
|
||||
neighbors[i].scopes = "DEN";
|
||||
@@ -435,8 +438,8 @@ TEST(MQTTPayloadBuilder, NeighborsMessageRendersUnknownHeardAgeAsNull) {
|
||||
// A neighbour stamped before the clock was set has no usable age. The key
|
||||
// stays present and null so a consumer cannot read it as "heard just now".
|
||||
MQTTPayloadBuilder::NeighborsMessageEntry neighbors[2];
|
||||
neighbors[0] = {"0011223344556677", 9.75f, 42, "DEN", "responded"};
|
||||
neighbors[1] = {"8899AABBCCDDEEFF", -3.5f, 0, "DEN", "responded"};
|
||||
neighbors[0] = {"0011223344556677", 9.75f, -87, 42, "DEN", "responded"};
|
||||
neighbors[1] = {"8899AABBCCDDEEFF", -3.5f, -110, 0, "DEN", "responded"};
|
||||
neighbors[1].heard_unknown = true;
|
||||
|
||||
JsonDocument scratch;
|
||||
@@ -463,7 +466,7 @@ TEST(MQTTPayloadBuilder, NeighborsMessageUnknownHeardAgeFitsMeasuredWidth) {
|
||||
// Paced discovery measures each entry with a known UINT32_MAX age; a null age
|
||||
// must never serialize wider than what that reserved.
|
||||
MQTTPayloadBuilder::NeighborsMessageEntry measured = {
|
||||
"0011223344556677", 9.75f, UINT32_MAX, "DEN", "responded"};
|
||||
"0011223344556677", 9.75f, -87, UINT32_MAX, "DEN", "responded"};
|
||||
MQTTPayloadBuilder::NeighborsMessageEntry unknown = measured;
|
||||
unknown.heard_unknown = true;
|
||||
unknown.heard_secs_ago = 0;
|
||||
|
||||
Reference in New Issue
Block a user