mirror of
https://github.com/agessaman/MeshCore.git
synced 2026-08-06 23:29:45 +00:00
For packets with a path set; auto try again if no echo was heard
This commit is contained in:
@@ -467,6 +467,34 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
---
|
||||
|
||||
#### View or change whether direct retries can fall back to the recently-heard repeater list
|
||||
**Usage:**
|
||||
- `get direct.retry.heard`
|
||||
- `set direct.retry.heard <state>`
|
||||
|
||||
**Parameters:**
|
||||
- `state`: `on`|`off`
|
||||
|
||||
**Default:** `off`
|
||||
|
||||
**Note:** When enabled, a repeater can use recently-heard non-duplicate repeater prefixes as a fallback for direct retry eligibility when no suitable neighbor entry is available.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the SNR margin used for direct retry eligibility
|
||||
**Usage:**
|
||||
- `get direct.retry.margin`
|
||||
- `set direct.retry.margin <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: Margin in dB above the SF-specific receive floor (minimum `0`, default `5`)
|
||||
|
||||
**Default:** `5`
|
||||
|
||||
**Note:** The retry gate uses the active SF floor of `SF5=-2.5`, `SF6=-5`, `SF7=-7.5`, `SF8=-10`, `SF9=-12.5`, `SF10=-15`, `SF11=-17.5`, `SF12=-20`, then adds this margin.
|
||||
|
||||
---
|
||||
|
||||
#### [Experimental] View or change the processing delay for received traffic
|
||||
**Usage:**
|
||||
- `get rxdelay`
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
#ifndef TXT_ACK_DELAY
|
||||
#define TXT_ACK_DELAY 200
|
||||
#endif
|
||||
#ifndef HALO_DIRECT_RETRY_DELAY_MIN
|
||||
#define HALO_DIRECT_RETRY_DELAY_MIN 200
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_VER_LEVEL 2
|
||||
|
||||
@@ -60,6 +63,20 @@
|
||||
|
||||
#define LAZY_CONTACTS_WRITE_DELAY 5000
|
||||
|
||||
const NeighbourInfo* MyMesh::findNeighbourByHash(const uint8_t* hash, uint8_t hash_len) const {
|
||||
#if MAX_NEIGHBOURS
|
||||
for (int i = 0; i < MAX_NEIGHBOURS; i++) {
|
||||
if (neighbours[i].heard_timestamp > 0 && neighbours[i].id.isHashMatch(hash, hash_len)) {
|
||||
return &neighbours[i];
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)hash;
|
||||
(void)hash_len;
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr) {
|
||||
#if MAX_NEIGHBOURS // check if neighbours enabled
|
||||
// find existing neighbour, else use least recently updated
|
||||
@@ -399,6 +416,8 @@ File MyMesh::openAppend(const char *fname) {
|
||||
static uint8_t max_loop_minimal[] = { 0, /* 1-byte */ 4, /* 2-byte */ 2, /* 3-byte */ 1 };
|
||||
static uint8_t max_loop_moderate[] = { 0, /* 1-byte */ 2, /* 2-byte */ 1, /* 3-byte */ 1 };
|
||||
static uint8_t max_loop_strict[] = { 0, /* 1-byte */ 1, /* 2-byte */ 1, /* 3-byte */ 1 };
|
||||
// SF5..SF12 receive floors, scaled by 4 so we can keep the retry gate in int8_t quarter-dB units.
|
||||
static const int8_t direct_retry_floor_x4[] = { -10, -20, -30, -40, -50, -60, -70, -80 };
|
||||
|
||||
bool MyMesh::isLooped(const mesh::Packet* packet, const uint8_t max_counters[]) {
|
||||
uint8_t hash_size = packet->getPathHashSize();
|
||||
@@ -531,6 +550,44 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
int8_t MyMesh::getDirectRetryMinSNRX4() const {
|
||||
// Use the live SF so `tempradio` changes immediately affect the retry threshold.
|
||||
uint8_t sf = constrain(active_sf, (uint8_t)5, (uint8_t)12);
|
||||
int16_t threshold = direct_retry_floor_x4[sf - 5] + ((int16_t)_prefs.direct_retry_snr_margin_db * 4);
|
||||
return (int8_t)constrain(threshold, -128, 127);
|
||||
}
|
||||
bool MyMesh::allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const {
|
||||
if (_prefs.disable_fwd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int8_t min_snr_x4 = getDirectRetryMinSNRX4();
|
||||
const NeighbourInfo* neighbour = findNeighbourByHash(next_hop_hash, next_hop_hash_len);
|
||||
// Prefer the explicit neighbor table first; it is the strongest signal that this hop is still reachable.
|
||||
if (neighbour != NULL && neighbour->snr >= min_snr_x4) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_prefs.direct_retry_recent_enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no neighbor entry exists, fall back to the recent-heard repeater cache keyed by the same path prefix.
|
||||
const auto* recent = ((const SimpleMeshTables *)getTables())->findRecentRepeaterByHash(next_hop_hash, next_hop_hash_len);
|
||||
return recent != NULL && recent->snr_x4 >= min_snr_x4;
|
||||
}
|
||||
uint32_t MyMesh::getDirectRetryEchoDelay(const mesh::Packet* packet) const {
|
||||
// Approximate LoRa line rate in kilobits/sec from the live radio params the repeater is using now.
|
||||
float kbps = (((float) active_sf) * active_bw * ((float) active_cr)) / ((float) (1UL << active_sf));
|
||||
if (kbps <= 0.0f) {
|
||||
return HALO_DIRECT_RETRY_DELAY_MIN;
|
||||
}
|
||||
|
||||
// Wait roughly long enough for our transmission, the next hop's receive/forward window, and its echo back.
|
||||
uint32_t bits = ((uint32_t) packet->getRawLength()) * 8;
|
||||
uint32_t scaled_wait_millis = (uint32_t) ((((float) bits) * 4.0f) / kbps);
|
||||
return max((uint32_t) HALO_DIRECT_RETRY_DELAY_MIN, scaled_wait_millis);
|
||||
}
|
||||
|
||||
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
|
||||
// just try to determine region for packet (apply later in allowPacketForward())
|
||||
@@ -859,6 +916,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
_prefs.direct_tx_delay_factor = 0.3f; // was 0.2
|
||||
_prefs.direct_retry_recent_enabled = 0;
|
||||
_prefs.direct_retry_snr_margin_db = 5;
|
||||
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
|
||||
_prefs.node_lat = ADVERT_LAT;
|
||||
_prefs.node_lon = ADVERT_LON;
|
||||
@@ -899,6 +958,9 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
|
||||
pending_discover_tag = 0;
|
||||
pending_discover_until = 0;
|
||||
active_bw = _prefs.bw;
|
||||
active_sf = _prefs.sf;
|
||||
active_cr = _prefs.cr;
|
||||
}
|
||||
|
||||
void MyMesh::begin(FILESYSTEM *fs) {
|
||||
@@ -917,6 +979,9 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
#endif
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
active_bw = _prefs.bw;
|
||||
active_sf = _prefs.sf;
|
||||
active_cr = _prefs.cr;
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
|
||||
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
||||
@@ -1314,12 +1379,18 @@ void MyMesh::loop() {
|
||||
if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params
|
||||
set_radio_at = 0; // clear timer
|
||||
radio_set_params(pending_freq, pending_bw, pending_sf, pending_cr);
|
||||
active_bw = pending_bw;
|
||||
active_sf = pending_sf;
|
||||
active_cr = pending_cr;
|
||||
MESH_DEBUG_PRINTLN("Temp radio params");
|
||||
}
|
||||
|
||||
if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig
|
||||
revert_radio_at = 0; // clear timer
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
active_bw = _prefs.bw;
|
||||
active_sf = _prefs.sf;
|
||||
active_cr = _prefs.cr;
|
||||
MESH_DEBUG_PRINTLN("Radio params restored");
|
||||
}
|
||||
|
||||
|
||||
@@ -109,8 +109,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
unsigned long set_radio_at, revert_radio_at;
|
||||
float pending_freq;
|
||||
float pending_bw;
|
||||
float active_bw; // live BW, including temporary radio overrides
|
||||
uint8_t pending_sf;
|
||||
uint8_t active_sf; // live SF, including temporary radio overrides
|
||||
uint8_t pending_cr;
|
||||
uint8_t active_cr; // live CR, including temporary radio overrides
|
||||
int matching_peer_indexes[MAX_CLIENTS];
|
||||
#if defined(WITH_RS232_BRIDGE)
|
||||
RS232Bridge bridge;
|
||||
@@ -118,6 +121,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
ESPNowBridge bridge;
|
||||
#endif
|
||||
|
||||
const NeighbourInfo* findNeighbourByHash(const uint8_t* hash, uint8_t hash_len) const;
|
||||
int8_t getDirectRetryMinSNRX4() const;
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
||||
void sendNodeDiscoverReq();
|
||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
|
||||
@@ -146,6 +151,8 @@ protected:
|
||||
|
||||
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
|
||||
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
|
||||
bool allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const override;
|
||||
uint32_t getDirectRetryEchoDelay(const mesh::Packet* packet) const override;
|
||||
|
||||
int getInterferenceThreshold() const override {
|
||||
return _prefs.interference_threshold;
|
||||
|
||||
+3
-1
@@ -106,6 +106,7 @@ void Dispatcher::loop() {
|
||||
|
||||
_radio->onSendFinished();
|
||||
logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
|
||||
onSendComplete(outbound);
|
||||
if (outbound->isRouteFlood()) {
|
||||
n_sent_flood++;
|
||||
} else {
|
||||
@@ -118,6 +119,7 @@ void Dispatcher::loop() {
|
||||
|
||||
_radio->onSendFinished();
|
||||
logTxFail(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
|
||||
onSendFail(outbound);
|
||||
|
||||
releasePacket(outbound); // return to pool
|
||||
outbound = NULL;
|
||||
@@ -386,4 +388,4 @@ unsigned long Dispatcher::futureMillis(int millis_from_now) const {
|
||||
return _ms->getMillis() + millis_from_now;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +159,8 @@ protected:
|
||||
virtual void logRx(Packet* packet, int len, float score) { } // hooks for custom logging
|
||||
virtual void logTx(Packet* packet, int len) { }
|
||||
virtual void logTxFail(Packet* packet, int len) { }
|
||||
virtual void onSendComplete(Packet* packet) { }
|
||||
virtual void onSendFail(Packet* packet) { }
|
||||
virtual const char* getLogDateTime() { return ""; }
|
||||
|
||||
virtual float getAirtimeBudgetFactor() const;
|
||||
|
||||
+265
-6
@@ -4,11 +4,32 @@
|
||||
namespace mesh {
|
||||
|
||||
void Mesh::begin() {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
_direct_retries[i].packet = NULL;
|
||||
_direct_retries[i].trigger_packet = NULL;
|
||||
_direct_retries[i].retry_at = 0;
|
||||
_direct_retries[i].retry_delay = 0;
|
||||
_direct_retries[i].priority = 0;
|
||||
_direct_retries[i].progress_marker = 0;
|
||||
_direct_retries[i].expect_path_growth = false;
|
||||
_direct_retries[i].queued = false;
|
||||
_direct_retries[i].active = false;
|
||||
}
|
||||
Dispatcher::begin();
|
||||
}
|
||||
|
||||
void Mesh::loop() {
|
||||
Dispatcher::loop();
|
||||
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active || !_direct_retries[i].queued || !millisHasNowPassed(_direct_retries[i].retry_at)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isDirectRetryQueued(_direct_retries[i].packet)) {
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Mesh::allowPacketForward(const mesh::Packet* packet) {
|
||||
@@ -22,10 +43,25 @@ uint32_t Mesh::getRetransmitDelay(const mesh::Packet* packet) {
|
||||
uint32_t Mesh::getDirectRetransmitDelay(const Packet* packet) {
|
||||
return 0; // by default, no delay
|
||||
}
|
||||
bool Mesh::allowDirectRetry(const Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const {
|
||||
return false;
|
||||
}
|
||||
uint32_t Mesh::getDirectRetryEchoDelay(const Packet* packet) const {
|
||||
// Keep the base fallback aligned with the repeater's minimum retry wait.
|
||||
return 200;
|
||||
}
|
||||
uint8_t Mesh::getExtraAckTransmitCount() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Mesh::onSendComplete(Packet* packet) {
|
||||
armDirectRetryOnSendComplete(packet);
|
||||
}
|
||||
|
||||
void Mesh::onSendFail(Packet* packet) {
|
||||
clearPendingDirectRetryOnSendFail(packet);
|
||||
}
|
||||
|
||||
uint32_t Mesh::getCADFailRetryDelay() const {
|
||||
return _rng->nextInt(1, 4)*120;
|
||||
}
|
||||
@@ -39,6 +75,10 @@ int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int
|
||||
}
|
||||
|
||||
DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
if (pkt->isRouteDirect()) {
|
||||
cancelDirectRetryOnEcho(pkt);
|
||||
}
|
||||
|
||||
if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
if (pkt->path_len < MAX_PATH_SIZE) {
|
||||
uint8_t i = 0;
|
||||
@@ -58,6 +98,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4);
|
||||
|
||||
uint32_t d = getDirectRetransmitDelay(pkt);
|
||||
maybeScheduleDirectRetry(pkt, 5);
|
||||
return ACTION_RETRANSMIT_DELAYED(5, d); // schedule with priority 5 (for now), maybe make configurable?
|
||||
}
|
||||
}
|
||||
@@ -98,6 +139,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
removeSelfFromPath(pkt);
|
||||
|
||||
uint32_t d = getDirectRetransmitDelay(pkt);
|
||||
maybeScheduleDirectRetry(pkt, 0);
|
||||
return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority
|
||||
}
|
||||
}
|
||||
@@ -372,6 +414,7 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
|
||||
a1->path_len = Packet::copyPath(a1->path, packet->path, packet->path_len);
|
||||
a1->header &= ~PH_ROUTE_MASK;
|
||||
a1->header |= ROUTE_TYPE_DIRECT;
|
||||
maybeScheduleDirectRetry(a1, 0);
|
||||
sendPacket(a1, 0, delay_millis);
|
||||
}
|
||||
extra--;
|
||||
@@ -382,11 +425,225 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
|
||||
a2->path_len = Packet::copyPath(a2->path, packet->path, packet->path_len);
|
||||
a2->header &= ~PH_ROUTE_MASK;
|
||||
a2->header |= ROUTE_TYPE_DIRECT;
|
||||
maybeScheduleDirectRetry(a2, 0);
|
||||
sendPacket(a2, 0, delay_millis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::clearDirectRetrySlot(int idx) {
|
||||
_direct_retries[idx].packet = NULL;
|
||||
_direct_retries[idx].trigger_packet = NULL;
|
||||
_direct_retries[idx].retry_at = 0;
|
||||
_direct_retries[idx].retry_delay = 0;
|
||||
_direct_retries[idx].priority = 0;
|
||||
_direct_retries[idx].progress_marker = 0;
|
||||
_direct_retries[idx].expect_path_growth = false;
|
||||
_direct_retries[idx].queued = false;
|
||||
_direct_retries[idx].active = false;
|
||||
}
|
||||
|
||||
bool Mesh::isDirectRetryQueued(const Packet* packet) const {
|
||||
for (int i = 0; i < _mgr->getOutboundTotal(); i++) {
|
||||
if (_mgr->getOutboundByIdx(i) == packet) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Mesh::calculateDirectRetryKey(const Packet* packet, uint8_t* dest_key) const {
|
||||
uint8_t type = packet->getPayloadType();
|
||||
Utils::sha256(dest_key, MAX_HASH_SIZE, &type, 1, packet->payload, packet->payload_len);
|
||||
}
|
||||
|
||||
bool Mesh::cancelDirectRetryOnEcho(const Packet* packet) {
|
||||
uint8_t recv_key[MAX_HASH_SIZE];
|
||||
calculateDirectRetryKey(packet, recv_key);
|
||||
|
||||
bool cleared = false;
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active || memcmp(recv_key, _direct_retries[i].retry_key, MAX_HASH_SIZE) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool is_echo = _direct_retries[i].expect_path_growth
|
||||
? packet->path_len > _direct_retries[i].progress_marker
|
||||
: packet->getPathHashCount() < _direct_retries[i].progress_marker;
|
||||
if (!is_echo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].queued) {
|
||||
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
|
||||
if (_mgr->getOutboundByIdx(j) == _direct_retries[i].packet) {
|
||||
Packet* pending = _mgr->removeOutboundByIdx(j);
|
||||
if (pending) {
|
||||
releasePacket(pending);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
clearDirectRetrySlot(i);
|
||||
} else {
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
cleared = true;
|
||||
}
|
||||
|
||||
return cleared;
|
||||
}
|
||||
|
||||
void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].queued) {
|
||||
if (_direct_retries[i].packet == packet) {
|
||||
// The retry packet itself just finished transmitting; Dispatcher will release it after this hook.
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].trigger_packet != packet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allocate the retry packet only after TX-complete so busy repeaters do not reserve pool slots early.
|
||||
Packet* retry = obtainNewPacket();
|
||||
if (retry == NULL) {
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
*retry = *packet;
|
||||
|
||||
// Start the echo wait only after the initial direct transmission actually completed.
|
||||
sendPacket(retry, _direct_retries[i].priority, _direct_retries[i].retry_delay);
|
||||
if (isDirectRetryQueued(retry)) {
|
||||
_direct_retries[i].packet = retry;
|
||||
_direct_retries[i].trigger_packet = NULL;
|
||||
_direct_retries[i].queued = true;
|
||||
_direct_retries[i].retry_at = futureMillis(_direct_retries[i].retry_delay);
|
||||
} else {
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::clearPendingDirectRetryOnSendFail(const Packet* packet) {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].queued) {
|
||||
if (_direct_retries[i].packet == packet) {
|
||||
// The queued retry itself failed; Dispatcher will release it after this hook.
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].trigger_packet == packet) {
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Mesh::getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_hash, uint8_t& next_hop_hash_len,
|
||||
uint8_t& progress_marker, bool& expect_path_growth) const {
|
||||
switch (packet->getPayloadType()) {
|
||||
case PAYLOAD_TYPE_ACK:
|
||||
case PAYLOAD_TYPE_PATH:
|
||||
case PAYLOAD_TYPE_REQ:
|
||||
case PAYLOAD_TYPE_RESPONSE:
|
||||
case PAYLOAD_TYPE_TXT_MSG:
|
||||
case PAYLOAD_TYPE_ANON_REQ:
|
||||
if (packet->getPathHashCount() <= 1) {
|
||||
return false;
|
||||
}
|
||||
next_hop_hash = packet->path;
|
||||
next_hop_hash_len = packet->getPathHashSize();
|
||||
progress_marker = packet->getPathHashCount();
|
||||
expect_path_growth = false;
|
||||
return true;
|
||||
|
||||
case PAYLOAD_TYPE_MULTIPART:
|
||||
if (packet->payload_len < 1 || (packet->payload[0] & 0x0F) != PAYLOAD_TYPE_ACK || packet->getPathHashCount() <= 1) {
|
||||
return false;
|
||||
}
|
||||
next_hop_hash = packet->path;
|
||||
next_hop_hash_len = packet->getPathHashSize();
|
||||
progress_marker = packet->getPathHashCount();
|
||||
expect_path_growth = false;
|
||||
return true;
|
||||
|
||||
case PAYLOAD_TYPE_TRACE: {
|
||||
if (packet->payload_len < 9) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t hash_size = 1 << (packet->payload[8] & 0x03);
|
||||
uint8_t route_bytes = packet->payload_len - 9;
|
||||
uint8_t offset = packet->path_len * hash_size;
|
||||
if (offset + hash_size > route_bytes) {
|
||||
return false;
|
||||
}
|
||||
if (offset + (2 * hash_size) > route_bytes) {
|
||||
return false; // no downstream repeater means there will be no forward echo to overhear.
|
||||
}
|
||||
|
||||
next_hop_hash = &packet->payload[9 + offset];
|
||||
next_hop_hash_len = hash_size;
|
||||
progress_marker = packet->path_len;
|
||||
expect_path_growth = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority) {
|
||||
const uint8_t* next_hop_hash;
|
||||
uint8_t next_hop_hash_len;
|
||||
uint8_t progress_marker;
|
||||
bool expect_path_growth;
|
||||
if (!getDirectRetryTarget(packet, next_hop_hash, next_hop_hash_len, progress_marker, expect_path_growth)
|
||||
|| !allowDirectRetry(packet, next_hop_hash, next_hop_hash_len)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int slot_idx = -1;
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) {
|
||||
slot_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot_idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only store retry metadata here; allocate the retry packet after the initial TX really completes.
|
||||
uint32_t retry_delay = getDirectRetryEchoDelay(packet);
|
||||
calculateDirectRetryKey(packet, _direct_retries[slot_idx].retry_key);
|
||||
_direct_retries[slot_idx].packet = NULL;
|
||||
_direct_retries[slot_idx].trigger_packet = const_cast<Packet*>(packet);
|
||||
_direct_retries[slot_idx].retry_at = 0;
|
||||
_direct_retries[slot_idx].retry_delay = retry_delay;
|
||||
_direct_retries[slot_idx].priority = priority;
|
||||
_direct_retries[slot_idx].progress_marker = progress_marker;
|
||||
_direct_retries[slot_idx].expect_path_growth = expect_path_growth;
|
||||
_direct_retries[slot_idx].queued = false;
|
||||
_direct_retries[slot_idx].active = true;
|
||||
}
|
||||
|
||||
Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) {
|
||||
if (app_data_len > MAX_ADVERT_DATA_SIZE) return NULL;
|
||||
|
||||
@@ -634,7 +891,7 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_si
|
||||
packet->header |= ROUTE_TYPE_FLOOD;
|
||||
packet->setPathHashSizeAndCount(path_hash_size, 0);
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
_tables->markSent(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
uint8_t pri;
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
|
||||
@@ -663,7 +920,7 @@ void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_m
|
||||
packet->transport_codes[1] = transport_codes[1];
|
||||
packet->setPathHashSizeAndCount(path_hash_size, 0);
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
_tables->markSent(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
uint8_t pri;
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
|
||||
@@ -696,7 +953,8 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin
|
||||
pri = 0;
|
||||
}
|
||||
}
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
_tables->markSent(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
maybeScheduleDirectRetry(packet, pri);
|
||||
sendPacket(packet, pri, delay_millis);
|
||||
}
|
||||
|
||||
@@ -706,7 +964,7 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) {
|
||||
|
||||
packet->path_len = 0; // path_len of zero means Zero Hop
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
_tables->markSent(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
sendPacket(packet, 0, delay_millis);
|
||||
}
|
||||
@@ -719,9 +977,10 @@ void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay
|
||||
|
||||
packet->path_len = 0; // path_len of zero means Zero Hop
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
_tables->markSent(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
sendPacket(packet, 0, delay_millis);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+41
@@ -4,6 +4,10 @@
|
||||
|
||||
namespace mesh {
|
||||
|
||||
#ifndef MAX_DIRECT_RETRY_SLOTS
|
||||
#define MAX_DIRECT_RETRY_SLOTS 6
|
||||
#endif
|
||||
|
||||
class GroupChannel {
|
||||
public:
|
||||
uint8_t hash[PATH_HASH_SIZE];
|
||||
@@ -16,6 +20,7 @@ public:
|
||||
class MeshTables {
|
||||
public:
|
||||
virtual bool hasSeen(const Packet* packet) = 0;
|
||||
virtual void markSent(const Packet* packet) = 0;
|
||||
virtual void clear(const Packet* packet) = 0; // remove this packet hash from table
|
||||
};
|
||||
|
||||
@@ -24,17 +29,42 @@ public:
|
||||
* and provides virtual methods for sub-classes on handling incoming, and also preparing outbound Packets.
|
||||
*/
|
||||
class Mesh : public Dispatcher {
|
||||
struct DirectRetryEntry {
|
||||
Packet* packet;
|
||||
Packet* trigger_packet;
|
||||
unsigned long retry_at;
|
||||
uint32_t retry_delay;
|
||||
uint8_t retry_key[MAX_HASH_SIZE];
|
||||
uint8_t priority;
|
||||
uint8_t progress_marker;
|
||||
bool expect_path_growth;
|
||||
bool queued;
|
||||
bool active;
|
||||
};
|
||||
|
||||
RTCClock* _rtc;
|
||||
RNG* _rng;
|
||||
MeshTables* _tables;
|
||||
DirectRetryEntry _direct_retries[MAX_DIRECT_RETRY_SLOTS];
|
||||
|
||||
void removeSelfFromPath(Packet* packet);
|
||||
void routeDirectRecvAcks(Packet* packet, uint32_t delay_millis);
|
||||
void clearDirectRetrySlot(int idx);
|
||||
bool isDirectRetryQueued(const Packet* packet) const;
|
||||
void calculateDirectRetryKey(const Packet* packet, uint8_t* dest_key) const;
|
||||
bool cancelDirectRetryOnEcho(const Packet* packet);
|
||||
void armDirectRetryOnSendComplete(const Packet* packet);
|
||||
void clearPendingDirectRetryOnSendFail(const Packet* packet);
|
||||
bool getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_hash, uint8_t& next_hop_hash_len,
|
||||
uint8_t& progress_marker, bool& expect_path_growth) const;
|
||||
void maybeScheduleDirectRetry(const Packet* packet, uint8_t priority);
|
||||
//void routeRecvAcks(Packet* packet, uint32_t delay_millis);
|
||||
DispatcherAction forwardMultipartDirect(Packet* pkt);
|
||||
|
||||
protected:
|
||||
DispatcherAction onRecvPacket(Packet* pkt) override;
|
||||
void onSendComplete(Packet* packet) override;
|
||||
void onSendFail(Packet* packet) override;
|
||||
|
||||
virtual uint32_t getCADFailRetryDelay() const override;
|
||||
|
||||
@@ -65,6 +95,17 @@ protected:
|
||||
*/
|
||||
virtual uint32_t getDirectRetransmitDelay(const Packet* packet);
|
||||
|
||||
/**
|
||||
* \brief Decide whether a DIRECT packet should get one delayed retry if the next hop echo is not overheard.
|
||||
* Sub-classes can use neighbour tables or other link-quality data to opt in selectively.
|
||||
*/
|
||||
virtual bool allowDirectRetry(const Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const;
|
||||
|
||||
/**
|
||||
* \returns milliseconds to wait for the next-hop echo before queueing one retry of the DIRECT packet.
|
||||
*/
|
||||
virtual uint32_t getDirectRetryEchoDelay(const Packet* packet) const;
|
||||
|
||||
/**
|
||||
* \returns number of extra (Direct) ACK transmissions wanted.
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
#define BRIDGE_MAX_BAUD 115200
|
||||
#endif
|
||||
|
||||
// These bytes used to be reserved/unused in persisted prefs, so keep a marker before trusting them.
|
||||
#define DIRECT_RETRY_PREFS_MAGIC_0 0xD4
|
||||
#define DIRECT_RETRY_PREFS_MAGIC_1 0x52
|
||||
#define DIRECT_RETRY_RECENT_DEFAULT 0
|
||||
#define DIRECT_RETRY_SNR_MARGIN_DB_DEFAULT 5
|
||||
#define DIRECT_RETRY_SNR_MARGIN_DB_MAX 40
|
||||
|
||||
// Believe it or not, this std C function is busted on some platforms!
|
||||
static uint32_t _atoi(const char* sp) {
|
||||
uint32_t n = 0;
|
||||
@@ -60,7 +67,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
file.read((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.read((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.read((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.read(pad, 4); // 108 : 4 bytes unused
|
||||
file.read((uint8_t *)&_prefs->direct_retry_recent_enabled, sizeof(_prefs->direct_retry_recent_enabled)); // 108
|
||||
file.read((uint8_t *)&_prefs->direct_retry_snr_margin_db, sizeof(_prefs->direct_retry_snr_margin_db)); // 109
|
||||
file.read((uint8_t *)&_prefs->direct_retry_prefs_magic[0], sizeof(_prefs->direct_retry_prefs_magic)); // 110
|
||||
file.read((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.read((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.read((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
@@ -102,6 +111,15 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
|
||||
_prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f);
|
||||
_prefs->path_hash_mode = constrain(_prefs->path_hash_mode, 0, 2); // NOTE: mode 3 reserved for future
|
||||
// Old firmware left offset 108..111 undefined, so require the marker before using the new retry prefs.
|
||||
if (_prefs->direct_retry_prefs_magic[0] != DIRECT_RETRY_PREFS_MAGIC_0
|
||||
|| _prefs->direct_retry_prefs_magic[1] != DIRECT_RETRY_PREFS_MAGIC_1) {
|
||||
_prefs->direct_retry_recent_enabled = DIRECT_RETRY_RECENT_DEFAULT;
|
||||
_prefs->direct_retry_snr_margin_db = DIRECT_RETRY_SNR_MARGIN_DB_DEFAULT;
|
||||
} else {
|
||||
_prefs->direct_retry_recent_enabled = constrain(_prefs->direct_retry_recent_enabled, 0, 1);
|
||||
_prefs->direct_retry_snr_margin_db = constrain(_prefs->direct_retry_snr_margin_db, 0, DIRECT_RETRY_SNR_MARGIN_DB_MAX);
|
||||
}
|
||||
|
||||
// sanitise bad bridge pref values
|
||||
_prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
|
||||
@@ -150,7 +168,11 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.write((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.write((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.write(pad, 4); // 108 : 4 byte unused
|
||||
file.write((uint8_t *)&_prefs->direct_retry_recent_enabled, sizeof(_prefs->direct_retry_recent_enabled)); // 108
|
||||
file.write((uint8_t *)&_prefs->direct_retry_snr_margin_db, sizeof(_prefs->direct_retry_snr_margin_db)); // 109
|
||||
// Persist a marker so later loads can distinguish real values from legacy garbage in this reserved slot.
|
||||
uint8_t retry_magic[2] = { DIRECT_RETRY_PREFS_MAGIC_0, DIRECT_RETRY_PREFS_MAGIC_1 };
|
||||
file.write(retry_magic, sizeof(retry_magic)); // 110
|
||||
file.write((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.write((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.write((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
@@ -338,6 +360,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->flood_max);
|
||||
} else if (memcmp(config, "direct.txdelay", 14) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor));
|
||||
} else if (memcmp(config, "direct.retry.heard", 18) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->direct_retry_recent_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "direct.retry.margin", 19) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->direct_retry_snr_margin_db);
|
||||
} else if (memcmp(config, "owner.info", 10) == 0) {
|
||||
*reply++ = '>';
|
||||
*reply++ = ' ';
|
||||
@@ -587,6 +613,27 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else {
|
||||
strcpy(reply, "Error, cannot be negative");
|
||||
}
|
||||
} else if (memcmp(config, "direct.retry.heard ", 19) == 0) {
|
||||
if (memcmp(&config[19], "on", 2) == 0) {
|
||||
_prefs->direct_retry_recent_enabled = 1;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(&config[19], "off", 3) == 0) {
|
||||
_prefs->direct_retry_recent_enabled = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be on or off");
|
||||
}
|
||||
} else if (memcmp(config, "direct.retry.margin ", 20) == 0) {
|
||||
int db = atoi(&config[20]);
|
||||
if (db >= 0 && db <= DIRECT_RETRY_SNR_MARGIN_DB_MAX) {
|
||||
_prefs->direct_retry_snr_margin_db = (uint8_t)db;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
sprintf(reply, "Error, min 0 and max %d", DIRECT_RETRY_SNR_MARGIN_DB_MAX);
|
||||
}
|
||||
} else if (memcmp(config, "owner.info ", 11) == 0) {
|
||||
config += 11;
|
||||
char *dp = _prefs->owner_info;
|
||||
|
||||
@@ -32,7 +32,9 @@ struct NodePrefs { // persisted to file
|
||||
float tx_delay_factor;
|
||||
char guest_password[16];
|
||||
float direct_tx_delay_factor;
|
||||
uint32_t guard;
|
||||
uint8_t direct_retry_recent_enabled;
|
||||
uint8_t direct_retry_snr_margin_db;
|
||||
uint8_t direct_retry_prefs_magic[2];
|
||||
uint8_t sf;
|
||||
uint8_t cr;
|
||||
uint8_t allow_read_only;
|
||||
|
||||
+151
-23
@@ -8,13 +8,103 @@
|
||||
|
||||
#define MAX_PACKET_HASHES 128
|
||||
#define MAX_PACKET_ACKS 64
|
||||
#define MAX_RECENT_REPEATERS 64
|
||||
#define MAX_ROUTE_HASH_BYTES 3
|
||||
|
||||
class SimpleMeshTables : public mesh::MeshTables {
|
||||
public:
|
||||
struct RecentRepeaterInfo {
|
||||
// Just enough identity to match a next-hop path prefix plus the SNR that heard it.
|
||||
uint8_t prefix[MAX_ROUTE_HASH_BYTES];
|
||||
uint8_t prefix_len;
|
||||
int8_t snr_x4;
|
||||
};
|
||||
|
||||
private:
|
||||
uint8_t _hashes[MAX_PACKET_HASHES*MAX_HASH_SIZE];
|
||||
int _next_idx;
|
||||
uint32_t _acks[MAX_PACKET_ACKS];
|
||||
int _next_ack_idx;
|
||||
uint32_t _direct_dups, _flood_dups;
|
||||
RecentRepeaterInfo _recent_repeaters[MAX_RECENT_REPEATERS];
|
||||
int _next_recent_repeater_idx;
|
||||
|
||||
bool hasSeenAck(uint32_t ack) const {
|
||||
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
|
||||
if (ack == _acks[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void storeAck(uint32_t ack) {
|
||||
_acks[_next_ack_idx] = ack;
|
||||
_next_ack_idx = (_next_ack_idx + 1) % MAX_PACKET_ACKS;
|
||||
}
|
||||
|
||||
bool hasSeenHash(const uint8_t* hash) const {
|
||||
const uint8_t* sp = _hashes;
|
||||
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
|
||||
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void storeHash(const uint8_t* hash) {
|
||||
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
|
||||
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES;
|
||||
}
|
||||
|
||||
bool extractRecentRepeater(const mesh::Packet* packet, uint8_t* prefix, uint8_t& prefix_len) const {
|
||||
// Learn repeater prefixes only from packet shapes that expose a trustworthy repeater ID.
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->payload_len >= PUB_KEY_SIZE) {
|
||||
memcpy(prefix, packet->payload, MAX_ROUTE_HASH_BYTES);
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_CONTROL
|
||||
&& packet->isRouteDirect()
|
||||
&& packet->getPathHashCount() == 0
|
||||
&& packet->payload_len >= 6 + MAX_ROUTE_HASH_BYTES
|
||||
&& (packet->payload[0] & 0xF0) == 0x90) {
|
||||
memcpy(prefix, &packet->payload[6], MAX_ROUTE_HASH_BYTES);
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (packet->isRouteFlood() && packet->getPathHashCount() > 0) {
|
||||
prefix_len = packet->getPathHashSize();
|
||||
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
}
|
||||
|
||||
const uint8_t* last_hop = &packet->path[(packet->getPathHashCount() - 1) * packet->getPathHashSize()];
|
||||
memcpy(prefix, last_hop, prefix_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void recordRecentRepeater(const mesh::Packet* packet) {
|
||||
uint8_t prefix[MAX_ROUTE_HASH_BYTES] = {0};
|
||||
uint8_t prefix_len = 0;
|
||||
if (!extractRecentRepeater(packet, prefix, prefix_len) || prefix_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ring buffer is enough here; retry fallback only needs a recent prefix->SNR observation.
|
||||
RecentRepeaterInfo& slot = _recent_repeaters[_next_recent_repeater_idx];
|
||||
memset(slot.prefix, 0, sizeof(slot.prefix));
|
||||
memcpy(slot.prefix, prefix, prefix_len);
|
||||
slot.prefix_len = prefix_len;
|
||||
slot.snr_x4 = packet->_snr;
|
||||
_next_recent_repeater_idx = (_next_recent_repeater_idx + 1) % MAX_RECENT_REPEATERS;
|
||||
}
|
||||
|
||||
public:
|
||||
SimpleMeshTables() {
|
||||
@@ -23,6 +113,8 @@ public:
|
||||
memset(_acks, 0, sizeof(_acks));
|
||||
_next_ack_idx = 0;
|
||||
_direct_dups = _flood_dups = 0;
|
||||
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
|
||||
_next_recent_repeater_idx = 0;
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
@@ -31,12 +123,16 @@ public:
|
||||
f.read((uint8_t *) &_next_idx, sizeof(_next_idx));
|
||||
f.read((uint8_t *) &_acks[0], sizeof(_acks));
|
||||
f.read((uint8_t *) &_next_ack_idx, sizeof(_next_ack_idx));
|
||||
f.read((uint8_t *) &_recent_repeaters[0], sizeof(_recent_repeaters));
|
||||
f.read((uint8_t *) &_next_recent_repeater_idx, sizeof(_next_recent_repeater_idx));
|
||||
}
|
||||
void saveTo(File f) {
|
||||
f.write(_hashes, sizeof(_hashes));
|
||||
f.write((const uint8_t *) &_next_idx, sizeof(_next_idx));
|
||||
f.write((const uint8_t *) &_acks[0], sizeof(_acks));
|
||||
f.write((const uint8_t *) &_next_ack_idx, sizeof(_next_ack_idx));
|
||||
f.write((const uint8_t *) &_recent_repeaters[0], sizeof(_recent_repeaters));
|
||||
f.write((const uint8_t *) &_next_recent_repeater_idx, sizeof(_next_recent_repeater_idx));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -44,28 +140,8 @@ public:
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
uint32_t ack;
|
||||
memcpy(&ack, packet->payload, 4);
|
||||
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
|
||||
if (ack == _acks[i]) {
|
||||
if (packet->isRouteDirect()) {
|
||||
_direct_dups++; // keep some stats
|
||||
} else {
|
||||
_flood_dups++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_acks[_next_ack_idx] = ack;
|
||||
_next_ack_idx = (_next_ack_idx + 1) % MAX_PACKET_ACKS; // cyclic table
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
|
||||
const uint8_t* sp = _hashes;
|
||||
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
|
||||
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
|
||||
if (hasSeenAck(ack)) {
|
||||
if (packet->isRouteDirect()) {
|
||||
_direct_dups++; // keep some stats
|
||||
} else {
|
||||
@@ -73,13 +149,46 @@ public:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
storeAck(ack);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
|
||||
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES; // cyclic table
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
|
||||
if (hasSeenHash(hash)) {
|
||||
if (packet->isRouteDirect()) {
|
||||
_direct_dups++; // keep some stats
|
||||
} else {
|
||||
_flood_dups++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
storeHash(hash);
|
||||
recordRecentRepeater(packet);
|
||||
return false;
|
||||
}
|
||||
|
||||
void markSent(const mesh::Packet* packet) override {
|
||||
// Outbound packets must be marked as already-sent without teaching the recent-heard cache about ourselves.
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
uint32_t ack;
|
||||
memcpy(&ack, packet->payload, 4);
|
||||
if (!hasSeenAck(ack)) {
|
||||
storeAck(ack);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
if (!hasSeenHash(hash)) {
|
||||
storeHash(hash);
|
||||
}
|
||||
}
|
||||
|
||||
void clear(const mesh::Packet* packet) override {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
uint32_t ack;
|
||||
@@ -107,5 +216,24 @@ public:
|
||||
uint32_t getNumDirectDups() const { return _direct_dups; }
|
||||
uint32_t getNumFloodDups() const { return _flood_dups; }
|
||||
|
||||
const RecentRepeaterInfo* findRecentRepeaterByHash(const uint8_t* hash, uint8_t hash_len) const {
|
||||
if (hash == NULL || hash_len == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Search newest-to-oldest so the retry gate prefers the freshest SNR sample for a prefix.
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
int idx = (_next_recent_repeater_idx - 1 - i + MAX_RECENT_REPEATERS) % MAX_RECENT_REPEATERS;
|
||||
const RecentRepeaterInfo* info = &_recent_repeaters[idx];
|
||||
if (info->prefix_len < hash_len || info->prefix_len == 0) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(info->prefix, hash, hash_len) == 0) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void resetStats() { _direct_dups = _flood_dups = 0; }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user