Keep ESP-NOW TX seen keys instead of duplicate packet snapshots
This commit is contained in:
1 parent
e13d24020a
commit
b8929c15fe
8 files changed
+171
-21
No files matched your search
@@ -53,6 +53,15 @@ inline bool routeHashPrefixesOverlap(const uint8_t* a, uint8_t a_len,
|
||||
|
||||
class SimpleMeshTables : public mesh::MeshTables {
|
||||
public:
|
||||
// A queued transport can retire an unsent admission after its Packet pool
|
||||
// entry is gone. Keep the complete hash and its separate ACK-cache domain.
|
||||
struct SeenKey {
|
||||
uint8_t hash[MAX_HASH_SIZE] = {};
|
||||
bool dedicated_ack = false;
|
||||
};
|
||||
static_assert(sizeof(SeenKey) == MAX_HASH_SIZE + 1,
|
||||
"A seen key must not retain packet data or alignment padding");
|
||||
|
||||
// Recent rows belong to the mesh loop, including CLI requests marshaled from
|
||||
// network tasks. They must not be read or changed concurrently by an ISR or
|
||||
// worker. Byte access avoids unaligned uint32_t loads in the compact rows;
|
||||
@@ -411,26 +420,32 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void clear(const mesh::Packet* packet) override {
|
||||
if (isDedicatedAckPacket(packet)) {
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
clearAckHash(hash);
|
||||
SeenKey seenKey(const mesh::Packet& packet) const {
|
||||
SeenKey key;
|
||||
packet.calculatePacketHash(key.hash);
|
||||
key.dedicated_ack = isDedicatedAckPacket(&packet);
|
||||
return key;
|
||||
}
|
||||
|
||||
void clearSeenKey(const SeenKey& key) {
|
||||
if (key.dedicated_ack) {
|
||||
clearAckHash(key.hash);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
|
||||
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 (memcmp(key.hash, sp, MAX_HASH_SIZE) == 0) {
|
||||
memset(sp, 0, MAX_HASH_SIZE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clear(const mesh::Packet* packet) override {
|
||||
clearSeenKey(seenKey(*packet));
|
||||
}
|
||||
|
||||
uint32_t getNumDirectDups() const { return _direct_dups; }
|
||||
uint32_t getNumFloodDups() const { return _flood_dups; }
|
||||
|
||||
|
||||
@@ -304,7 +304,8 @@ void ESPNowBridge::end() {
|
||||
uint8_t tx_index = _tx_tail;
|
||||
for (uint8_t i = 0; i < _tx_count; ++i) {
|
||||
const QueuedTransmit& queued_tx = _tx_queue[tx_index];
|
||||
if (!queued_tx.started && !queued_tx.diagnostic) _seen_packets.clear(&queued_tx.packet);
|
||||
if (!queued_tx.started && !queued_tx.diagnostic)
|
||||
_seen_packets.clearSeenKey(queued_tx.seen_key);
|
||||
tx_index = static_cast<uint8_t>((tx_index + 1) % TX_QUEUE_DEPTH);
|
||||
}
|
||||
portENTER_CRITICAL(&_tx_mux);
|
||||
@@ -385,12 +386,16 @@ bool ESPNowBridge::queueTransmitFrames(
|
||||
}
|
||||
}
|
||||
|
||||
// Hash the caller-owned snapshot before entering the callback-shared queue
|
||||
// lock. SHA work never extends an ESP-NOW critical section.
|
||||
SimpleMeshTables::SeenKey seen_key;
|
||||
if (!diagnostic) seen_key = _seen_packets.seenKey(packet);
|
||||
bool queued = false;
|
||||
portENTER_CRITICAL(&_tx_mux);
|
||||
if (_tx_count < TX_QUEUE_DEPTH) {
|
||||
QueuedTransmit& queued_tx = _tx_queue[_tx_head];
|
||||
queued_tx.frames = frames;
|
||||
if (!diagnostic) queued_tx.packet = packet;
|
||||
queued_tx.seen_key = seen_key;
|
||||
queued_tx.next_frame = 0;
|
||||
queued_tx.started = false;
|
||||
queued_tx.start_retries = 0;
|
||||
@@ -417,7 +422,7 @@ void ESPNowBridge::pumpTransmitQueue() {
|
||||
bool completed = false;
|
||||
int completed_status = ESP_NOW_SEND_SUCCESS;
|
||||
bool clear_unsent_seen = false;
|
||||
mesh::Packet unsent_packet;
|
||||
SimpleMeshTables::SeenKey unsent_key;
|
||||
uint32_t dropped = 0;
|
||||
const uint32_t now = millis();
|
||||
bool timed_out = false;
|
||||
@@ -527,7 +532,7 @@ void ESPNowBridge::pumpTransmitQueue() {
|
||||
_tx_retry_at = now + TX_RETRY_DELAY_MS;
|
||||
} else {
|
||||
if (!queued_tx.started && !queued_tx.diagnostic) {
|
||||
unsent_packet = queued_tx.packet;
|
||||
unsent_key = queued_tx.seen_key;
|
||||
clear_unsent_seen = true;
|
||||
}
|
||||
_tx_tail = static_cast<uint8_t>((_tx_tail + 1) % TX_QUEUE_DEPTH);
|
||||
@@ -539,7 +544,7 @@ void ESPNowBridge::pumpTransmitQueue() {
|
||||
portEXIT_CRITICAL(&_tx_mux);
|
||||
// Admission marks the packet to suppress duplicate queue entries. Restore
|
||||
// the prior behavior when even its first esp_now_send() cannot start.
|
||||
if (clear_unsent_seen) _seen_packets.clear(&unsent_packet);
|
||||
if (clear_unsent_seen) _seen_packets.clearSeenKey(unsent_key);
|
||||
BRIDGE_DEBUG_PRINTLN("TX failed to start, error=%d\n", (int)result);
|
||||
} else {
|
||||
portENTER_CRITICAL(&_tx_mux);
|
||||
|
||||
@@ -104,12 +104,15 @@ private:
|
||||
// instead of transmitting an orphan fragment.
|
||||
struct QueuedTransmit {
|
||||
mesh::espnow::ESPNowRawFrames frames;
|
||||
mesh::Packet packet;
|
||||
SimpleMeshTables::SeenKey seen_key;
|
||||
uint8_t next_frame;
|
||||
bool started;
|
||||
uint8_t start_retries;
|
||||
bool diagnostic;
|
||||
};
|
||||
static_assert(sizeof(QueuedTransmit) <= sizeof(mesh::espnow::ESPNowRawFrames) +
|
||||
sizeof(SimpleMeshTables::SeenKey) + 8,
|
||||
"ESP-NOW TX must retain encoded frames without a second packet copy");
|
||||
QueuedTransmit _tx_queue[TX_QUEUE_DEPTH];
|
||||
portMUX_TYPE _tx_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
volatile uint8_t _tx_head;
|
||||
|
||||
Reference in new issue
Block a user