Honor retry settings across firmware roles
This commit is contained in:
1 parent
4f839d3477
commit
b8f438d6ea
11 files changed
+356
-27
No files matched your search
+88
-16
@@ -140,6 +140,8 @@ uint8_t Mesh::getOtaHopLimit() const { return ota::ota_ctx().manager.max_hops();
|
||||
#endif
|
||||
|
||||
void Mesh::begin() {
|
||||
_active_direct_retry_count = 0;
|
||||
_active_flood_retry_count = 0;
|
||||
_waiting_direct_retry_count = 0;
|
||||
_waiting_flood_retry_count = 0;
|
||||
_next_direct_retry_timeout = 0;
|
||||
@@ -866,6 +868,9 @@ void Mesh::clearDirectRetrySlot(int idx) {
|
||||
&& _waiting_direct_retry_count > 0) {
|
||||
_waiting_direct_retry_count--;
|
||||
}
|
||||
if (_direct_retries[idx].active && _active_direct_retry_count > 0) {
|
||||
_active_direct_retry_count--;
|
||||
}
|
||||
_direct_retries[idx].packet = NULL;
|
||||
_direct_retries[idx].trigger_packet = NULL;
|
||||
_direct_retries[idx].retry_started_at = 0;
|
||||
@@ -905,10 +910,12 @@ void Mesh::rebuildNextDirectRetryTimeout() {
|
||||
}
|
||||
|
||||
bool Mesh::usePassiveChannelCheck(const Packet* packet) const {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (_direct_retries[i].active && _direct_retries[i].queued
|
||||
&& _direct_retries[i].packet == packet) {
|
||||
return true;
|
||||
if (_active_direct_retry_count != 0) {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (_direct_retries[i].active && _direct_retries[i].queued
|
||||
&& _direct_retries[i].packet == packet) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -917,10 +924,12 @@ bool Mesh::usePassiveChannelCheck(const Packet* packet) const {
|
||||
// CAD here too, since restarting RX can hide that echo. The initial flood
|
||||
// has trigger_packet set but queued=false, so ordinary flood forwarding
|
||||
// continues to use the normal CAD check.
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
if (_flood_retries[i].active && _flood_retries[i].queued
|
||||
&& _flood_retries[i].packet == packet) {
|
||||
return true;
|
||||
if (_active_flood_retry_count != 0) {
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
if (_flood_retries[i].active && _flood_retries[i].queued
|
||||
&& _flood_retries[i].packet == packet) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,6 +963,8 @@ void Mesh::calculateDirectRetryKey(const Packet* packet, uint8_t* dest_key) cons
|
||||
}
|
||||
|
||||
bool Mesh::cancelDirectRetryOnEcho(const Packet* packet) {
|
||||
if (_active_direct_retry_count == 0) return false;
|
||||
|
||||
uint8_t recv_key[MAX_HASH_SIZE];
|
||||
calculateDirectRetryKey(packet, recv_key);
|
||||
|
||||
@@ -1016,6 +1027,8 @@ bool Mesh::cancelDirectRetryOnEcho(const Packet* packet) {
|
||||
}
|
||||
|
||||
void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
if (_active_direct_retry_count == 0) return;
|
||||
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) {
|
||||
continue;
|
||||
@@ -1136,6 +1149,8 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
}
|
||||
|
||||
void Mesh::clearPendingDirectRetryOnSendFail(const Packet* packet) {
|
||||
if (_active_direct_retry_count == 0) return;
|
||||
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) {
|
||||
continue;
|
||||
@@ -1339,6 +1354,7 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority, bool
|
||||
_direct_retries[slot_idx].waiting_final_echo = false;
|
||||
_direct_retries[slot_idx].queued = false;
|
||||
_direct_retries[slot_idx].active = true;
|
||||
_active_direct_retry_count++;
|
||||
}
|
||||
|
||||
void Mesh::clearFloodRetrySlot(int idx) {
|
||||
@@ -1346,6 +1362,9 @@ void Mesh::clearFloodRetrySlot(int idx) {
|
||||
&& _flood_retries[idx].waiting_final_echo
|
||||
&& _flood_retries[idx].retry_at == _next_flood_retry_timeout;
|
||||
if (_flood_retries[idx].active) {
|
||||
if (_active_flood_retry_count > 0) {
|
||||
_active_flood_retry_count--;
|
||||
}
|
||||
if (_flood_retries[idx].waiting_final_echo && _waiting_flood_retry_count > 0) {
|
||||
_waiting_flood_retry_count--;
|
||||
}
|
||||
@@ -1386,8 +1405,52 @@ void Mesh::rebuildNextFloodRetryTimeout() {
|
||||
if (!found) _next_flood_retry_timeout = 0;
|
||||
}
|
||||
|
||||
void Mesh::cancelAllDirectRetries() {
|
||||
if (_active_direct_retry_count == 0) return;
|
||||
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) continue;
|
||||
|
||||
Packet* retry = _direct_retries[i].queued ? _direct_retries[i].packet : NULL;
|
||||
if (retry != NULL && retry != getOutboundInFlight()) {
|
||||
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
|
||||
if (_mgr->getOutboundByIdx(j) != retry) continue;
|
||||
Packet* pending = _mgr->removeOutboundByIdx(j);
|
||||
if (pending != NULL) {
|
||||
_direct_retries[i].packet = NULL;
|
||||
releasePacket(pending);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::cancelAllFloodRetries() {
|
||||
if (_active_flood_retry_count == 0) return;
|
||||
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
if (!_flood_retries[i].active) continue;
|
||||
|
||||
Packet* retry = _flood_retries[i].queued ? _flood_retries[i].packet : NULL;
|
||||
if (retry != NULL && retry != getOutboundInFlight()) {
|
||||
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
|
||||
if (_mgr->getOutboundByIdx(j) != retry) continue;
|
||||
Packet* pending = _mgr->removeOutboundByIdx(j);
|
||||
if (pending != NULL) {
|
||||
_flood_retries[i].packet = NULL;
|
||||
releasePacket(pending);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
clearFloodRetrySlot(i);
|
||||
}
|
||||
}
|
||||
|
||||
bool Mesh::cancelActiveRetries(const uint8_t retry_key[MAX_HASH_SIZE]) {
|
||||
if (retry_key == NULL) {
|
||||
if (retry_key == NULL || (_active_direct_retry_count == 0 && _active_flood_retry_count == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1444,7 +1507,7 @@ bool Mesh::cancelActiveRetries(const uint8_t retry_key[MAX_HASH_SIZE]) {
|
||||
}
|
||||
|
||||
bool Mesh::hasActiveRetries(const uint8_t retry_key[MAX_HASH_SIZE]) const {
|
||||
if (retry_key == NULL) {
|
||||
if (retry_key == NULL || (_active_direct_retry_count == 0 && _active_flood_retry_count == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1468,6 +1531,8 @@ bool Mesh::isFloodRetryEchoTarget(const Packet* packet, uint8_t progress_marker)
|
||||
}
|
||||
|
||||
bool Mesh::cancelFloodRetryOnEcho(const Packet* packet) {
|
||||
if (_active_flood_retry_count == 0) return false;
|
||||
|
||||
uint8_t recv_key[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(recv_key);
|
||||
|
||||
@@ -1507,6 +1572,8 @@ bool Mesh::cancelFloodRetryOnEcho(const Packet* packet) {
|
||||
}
|
||||
|
||||
void Mesh::armFloodRetryOnSendComplete(const Packet* packet) {
|
||||
if (_active_flood_retry_count == 0) return;
|
||||
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
if (!_flood_retries[i].active) {
|
||||
continue;
|
||||
@@ -1604,6 +1671,8 @@ void Mesh::armFloodRetryOnSendComplete(const Packet* packet) {
|
||||
}
|
||||
|
||||
void Mesh::clearPendingFloodRetryOnSendFail(const Packet* packet) {
|
||||
if (_active_flood_retry_count == 0) return;
|
||||
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
if (!_flood_retries[i].active) {
|
||||
continue;
|
||||
@@ -1627,7 +1696,14 @@ void Mesh::clearPendingFloodRetryOnSendFail(const Packet* packet) {
|
||||
}
|
||||
|
||||
void Mesh::maybeScheduleFloodRetry(const Packet* packet, uint8_t priority) {
|
||||
if (packet == NULL || !packet->isRouteFlood() || hasFloodRetryTargetPrefix(packet)) {
|
||||
if (packet == NULL || !packet->isRouteFlood()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the inexpensive global kill switch before prefix/path eligibility.
|
||||
// This makes flood.retry.count=0 a genuinely cheap disabled state.
|
||||
uint8_t max_attempts = getFloodRetryMaxAttempts(packet);
|
||||
if (max_attempts == 0 || hasFloodRetryTargetPrefix(packet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1636,11 +1712,6 @@ void Mesh::maybeScheduleFloodRetry(const Packet* packet, uint8_t priority) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t max_attempts = getFloodRetryMaxAttempts(packet);
|
||||
if (max_attempts == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t retry_key[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(retry_key);
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
@@ -1680,6 +1751,7 @@ void Mesh::maybeScheduleFloodRetry(const Packet* packet, uint8_t priority) {
|
||||
_flood_retries[slot_idx].waiting_final_echo = false;
|
||||
_flood_retries[slot_idx].queued = false;
|
||||
_flood_retries[slot_idx].active = true;
|
||||
_active_flood_retry_count++;
|
||||
}
|
||||
|
||||
Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) {
|
||||
|
||||
@@ -92,6 +92,8 @@ class Mesh : public Dispatcher {
|
||||
MeshTables* _tables;
|
||||
DirectRetryEntry _direct_retries[MAX_DIRECT_RETRY_SLOTS];
|
||||
FloodRetryEntry _flood_retries[MAX_FLOOD_RETRY_SLOTS];
|
||||
uint8_t _active_direct_retry_count;
|
||||
uint8_t _active_flood_retry_count;
|
||||
uint8_t _waiting_direct_retry_count;
|
||||
uint8_t _waiting_flood_retry_count;
|
||||
unsigned long _next_direct_retry_timeout;
|
||||
@@ -202,6 +204,11 @@ protected:
|
||||
*/
|
||||
bool cancelActiveRetries(const uint8_t retry_key[MAX_HASH_SIZE]);
|
||||
|
||||
// Cancel every queued/future retry of one route type. A packet already on
|
||||
// air is allowed to finish, but no later retry is armed from it.
|
||||
void cancelAllDirectRetries();
|
||||
void cancelAllFloodRetries();
|
||||
|
||||
/**
|
||||
* \returns true while a direct or flood retry sequence owns this packet payload hash.
|
||||
*/
|
||||
|
||||
@@ -146,6 +146,32 @@ static bool looksUnsignedInteger(const char* s) {
|
||||
return saw_digit;
|
||||
}
|
||||
|
||||
static bool configKeyMatches(const char* config, const char* key) {
|
||||
if (config == NULL || key == NULL) return false;
|
||||
size_t key_len = strlen(key);
|
||||
return strncmp(config, key, key_len) == 0
|
||||
&& (config[key_len] == 0 || config[key_len] == ' ' || config[key_len] == '.');
|
||||
}
|
||||
|
||||
static bool isAdvancedRetryConfig(const char* config) {
|
||||
return configKeyMatches(config, "direct.retry.heard")
|
||||
|| configKeyMatches(config, "direct.retry.margin")
|
||||
|| configKeyMatches(config, "flood.retry.prefixes")
|
||||
|| configKeyMatches(config, "flood.retry.ignore")
|
||||
|| configKeyMatches(config, "flood.retry.bridge")
|
||||
|| configKeyMatches(config, "flood.retry.bucket")
|
||||
|| configKeyMatches(config, "recent.repeater")
|
||||
|| configKeyMatches(config, "recent.repeaters");
|
||||
}
|
||||
|
||||
static bool isBasicRetryConfig(const char* config) {
|
||||
return configKeyMatches(config, "retry.preset")
|
||||
|| configKeyMatches(config, "direct.retry")
|
||||
|| configKeyMatches(config, "flood.retry.count")
|
||||
|| configKeyMatches(config, "flood.retry.path")
|
||||
|| configKeyMatches(config, "flood.retry.advert");
|
||||
}
|
||||
|
||||
static bool parseUint8Strict(const char* value, uint8_t min_value, uint8_t max_value, uint8_t& result) {
|
||||
if (value == NULL || *value == 0) {
|
||||
return false;
|
||||
@@ -1818,8 +1844,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
_callbacks->clearStats();
|
||||
strcpy(reply, "(OK - stats reset)");
|
||||
} else if (memcmp(command, "clear recent.repeater", 21) == 0 && (command[21] == 0 || command[21] == ' ')) {
|
||||
_callbacks->clearRecentRepeaters();
|
||||
strcpy(reply, "OK");
|
||||
if (_callbacks->supportsAdvancedRetryConfig()) {
|
||||
_callbacks->clearRecentRepeaters();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, unsupported on this role");
|
||||
}
|
||||
} else if (memcmp(command, "get ", 4) == 0) {
|
||||
handleGetCmd(sender_timestamp, command, reply);
|
||||
} else if (memcmp(command, "set ", 4) == 0) {
|
||||
@@ -2066,6 +2096,14 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
const char* config = &command[4];
|
||||
// Observer/MQTT/WiFi/timezone/alert/SNMP commands live in CommonCLI_Observer.cpp.
|
||||
if (handleObserverSetCmd(sender_timestamp, config, reply)) return;
|
||||
if (isAdvancedRetryConfig(config) && !_callbacks->supportsAdvancedRetryConfig()) {
|
||||
strcpy(reply, "Error, unsupported on this role");
|
||||
return;
|
||||
}
|
||||
if (isBasicRetryConfig(config) && !_callbacks->supportsBasicRetryConfig()) {
|
||||
strcpy(reply, "Error, retry configuration unsupported on this role");
|
||||
return;
|
||||
}
|
||||
if (memcmp(config, "dutycycle ", 10) == 0) {
|
||||
float dc = atof(&config[10]);
|
||||
if (dc < 1 || dc > 100) {
|
||||
@@ -2160,9 +2198,14 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
strcpy(reply, "Error, bad chars");
|
||||
}
|
||||
} else if (memcmp(config, "repeat ", 7) == 0) {
|
||||
_prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON");
|
||||
if (strcmp(&config[7], "on") == 0 || strcmp(&config[7], "off") == 0) {
|
||||
_prefs->disable_fwd = strcmp(&config[7], "off") == 0;
|
||||
savePrefs();
|
||||
_callbacks->onRetryConfigChanged();
|
||||
strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be on or off");
|
||||
}
|
||||
} else if (memcmp(config, "radio.rxgain ", 13) == 0) {
|
||||
bool enabled = memcmp(&config[13], "on", 2) == 0;
|
||||
if (_callbacks->setRxBoostedGain(enabled)) {
|
||||
@@ -2411,10 +2454,12 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
if (strcmp(&config[13], "on") == 0) {
|
||||
_prefs->direct_retry_enabled = 1;
|
||||
savePrefs();
|
||||
_callbacks->onRetryConfigChanged();
|
||||
strcpy(reply, "OK");
|
||||
} else if (strcmp(&config[13], "off") == 0) {
|
||||
_prefs->direct_retry_enabled = 0;
|
||||
savePrefs();
|
||||
_callbacks->onRetryConfigChanged();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be on or off");
|
||||
@@ -2558,6 +2603,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
_prefs->flood_retry_attempts = (uint8_t)attempts;
|
||||
_prefs->retry_preset = RETRY_PRESET_CUSTOM;
|
||||
savePrefs();
|
||||
_callbacks->onRetryConfigChanged();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be 0-15");
|
||||
@@ -2638,6 +2684,14 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
_prefs->direct_retry_cr_enabled = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (!_callbacks->supportsAdvancedRetryConfig()) {
|
||||
if (strcmp(&config[16], "on") == 0) {
|
||||
_prefs->direct_retry_cr_enabled = 1;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, use on or off on this role");
|
||||
}
|
||||
} else {
|
||||
strcpy(tmp, &config[16]);
|
||||
const char *parts[4];
|
||||
@@ -2837,6 +2891,14 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
const char* config = &command[4];
|
||||
// Observer/MQTT/WiFi/timezone/alert/SNMP commands live in CommonCLI_Observer.cpp.
|
||||
if (handleObserverGetCmd(sender_timestamp, config, reply)) return;
|
||||
if (isAdvancedRetryConfig(config) && !_callbacks->supportsAdvancedRetryConfig()) {
|
||||
strcpy(reply, "Error, unsupported on this role");
|
||||
return;
|
||||
}
|
||||
if (isBasicRetryConfig(config) && !_callbacks->supportsBasicRetryConfig()) {
|
||||
strcpy(reply, "Error, retry configuration unsupported on this role");
|
||||
return;
|
||||
}
|
||||
int recent_page = 1;
|
||||
if (memcmp(config, "dutycycle", 9) == 0) {
|
||||
float dc = 100.0f / (_prefs->airtime_factor + 1.0f);
|
||||
@@ -2988,6 +3050,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else if (memcmp(config, "direct.retry.cr", 15) == 0) {
|
||||
if (!_prefs->direct_retry_cr_enabled) {
|
||||
strcpy(reply, "> off");
|
||||
} else if (!_callbacks->supportsAdvancedRetryConfig()) {
|
||||
strcpy(reply, "> on");
|
||||
} else {
|
||||
char cr4[12], cr5[12], cr7[12], cr8[12];
|
||||
formatSnrDbX4(cr4, sizeof(cr4), _prefs->direct_retry_cr4_snr_x4);
|
||||
|
||||
@@ -447,6 +447,12 @@ public:
|
||||
}
|
||||
virtual void clearRecentRepeaters() {
|
||||
}
|
||||
// Basic retry controls are portable across roles (enable/count/timing,
|
||||
// flood count/path/advert, and the current-CR retry progression). Advanced
|
||||
// controls require the repeater's recent-SNR and bridge-routing tables.
|
||||
virtual bool supportsBasicRetryConfig() const { return false; }
|
||||
virtual bool supportsAdvancedRetryConfig() const { return false; }
|
||||
virtual void onRetryConfigChanged() { }
|
||||
virtual mesh::LocalIdentity& getSelfId() = 0;
|
||||
virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0;
|
||||
virtual void clearStats() = 0;
|
||||
|
||||
Reference in new issue
Block a user