sync with dev

This commit is contained in:
liquidraver
2026-07-02 13:13:26 +02:00
parent 4d16dc7589
commit fc940d27ba
16 changed files with 79 additions and 24 deletions
+1 -1
View File
@@ -203,7 +203,7 @@ Path_len byte:
**Direct routing**: Packet carries a source-routed `path[]`. Each relay node checks if the first path hash matches its own identity, removes itself, and forwards. Path is built from previous flood packets' accumulated hashes.
**Deduplication**: `SimpleMeshTables` maintains a circular buffer of 128 packet hashes (8 bytes each, SHA-256 truncated) and 64 ACK CRCs. `hasSeen()` prevents duplicate processing and retransmission.
**Deduplication**: `SimpleMeshTables` maintains a circular buffer of 160 packet hashes (8 bytes each, SHA-256 truncated); ACKs are deduped through the same packet-hash path. `wasSeen()` is a pure query; call sites insert explicitly via `markSeen()` to prevent duplicate processing and retransmission.
### 4.5 Dispatcher Scheduling
+1 -1
View File
@@ -247,7 +247,7 @@ Changes are persisted immediately unless noted. Some require a reboot.
| `set multi.acks <0\|1>` | | Enable extra ACK transmits |
| `set path.hash.mode <mode>` | 0, 1, or 2 | Path hashing algorithm |
| `set loop.detect <mode>` | `off`, `minimal`, `moderate`, `strict` | Loop detection sensitivity |
| `set radio.rxgain <0\|1\|on\|off>` | | RX gain boost *(reboot required)* |
| `set radio.rxgain <0\|1\|on\|off>` | | RX gain boost, applied live. Replies `Error: unsupported` on radios without RX boost (SX127x); the pref is still saved. |
| `set rxduty <0\|1\|on\|off>` | | RX duty cycle mode *(reboot required)*. Window timing auto-sized per SF/BW/preamble from the SX126x datasheet constraints (boot log line `rxduty:` shows the result). Zero-loss guarantee assumes senders on preamble-32 firmware (current MeshCore at SF≤8); legacy preamble-16 senders are only caught ~50% worst-phase — keep off until the local mesh has converted. Presets with 16-symbol preambles (SF≥9) fall back to continuous RX automatically. |
| `set adc.multiplier <mult>` | (0 = use board default) | Battery voltage ADC calibration multiplier |
| `set prv.key <hex>` | 64-char hex (32-byte key) | Replace private key; derive new identity *(reboot to apply)* |
+2 -1
View File
@@ -881,7 +881,7 @@ void LoRaRadioBase::enableRxDutyCycle(bool enable)
}
}
void LoRaRadioBase::setRxBoost(bool enable)
bool LoRaRadioBase::setRxBoost(bool enable)
{
_rx_boost_enabled = enable;
LOG_INF("RX boost %s (+3dB sensitivity, +2mA)",
@@ -889,6 +889,7 @@ void LoRaRadioBase::setRxBoost(bool enable)
if (atomic_get(&_in_recv_mode)) {
hwSetRxBoost(enable);
}
return true;
}
} /* namespace mesh */
+2 -1
View File
@@ -78,7 +78,8 @@ public:
/* Power saving */
void enableRxDutyCycle(bool enable);
bool isRxDutyCycleEnabled() const { return _rx_duty_cycle_enabled; }
void setRxBoost(bool enable);
/* Returns false when the chip has no RX boost feature (SX127x). */
virtual bool setRxBoost(bool enable);
bool isRxBoostEnabled() const { return _rx_boost_enabled; }
/* Duty-cycle preamble false-positive counter.
+7
View File
@@ -21,6 +21,13 @@ public:
void begin() override;
/* SX127x has no RX boost feature — report unsupported so the
* radio.rxgain CLI can reply "Error: unsupported". */
bool setRxBoost(bool enable) override {
(void)enable;
return false;
}
protected:
/* Hardware primitives */
bool hwConfigure(const struct lora_modem_config &cfg) override;
+4
View File
@@ -1146,6 +1146,10 @@ void RepeaterMesh::setTxPower(int8_t power_dbm) {
radio_set_tx_power(power_dbm);
}
bool RepeaterMesh::setRxBoostedGain(bool enable) {
return getRadioDriver(_radio).setRxBoost(enable);
}
void RepeaterMesh::formatNeighborsReply(char* reply) {
char* dp = reply;
+1
View File
@@ -233,6 +233,7 @@ public:
void eraseLogFile() override;
void dumpLogFile() override;
void setTxPower(int8_t power_dbm) override;
bool setRxBoostedGain(bool enable) override;
void formatNeighborsReply(char* reply) override;
void removeNeighbor(const uint8_t* pubkey, int key_len) override;
void formatStatsReply(char* reply) override;
+4
View File
@@ -918,6 +918,10 @@ void RoomServerMesh::setTxPower(int8_t power_dbm) {
radio_set_tx_power(power_dbm);
}
bool RoomServerMesh::setRxBoostedGain(bool enable) {
return getRadioDriver(_radio).setRxBoost(enable);
}
/* A room server keeps no neighbour table (it is not a repeater). */
void RoomServerMesh::formatNeighborsReply(char* reply) {
strcpy(reply, "not supported");
+1
View File
@@ -182,6 +182,7 @@ public:
void eraseLogFile() override;
void dumpLogFile() override;
void setTxPower(int8_t power_dbm) override;
bool setRxBoostedGain(bool enable) override;
void formatNeighborsReply(char* reply) override;
void formatStatsReply(char* reply) override;
void formatRadioStatsReply(char* reply) override;
+7 -1
View File
@@ -895,9 +895,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
else if (memcmp(arg, "off", 3) == 0) val = 0;
else if (arg[0] == '0' || arg[0] == '1') val = atoi(arg);
if (val == 0 || val == 1) {
/* Always save (upstream f3d4d8cd), then apply live and
* report when the radio has no RX boost feature. */
_prefs->rx_boost = (uint8_t)val;
savePrefs();
snprintf(reply, CLI_REPLY_SIZE, "OK - radio.rxgain=%d (reboot to apply)", _prefs->rx_boost);
if (_callbacks->setRxBoostedGain(val == 1)) {
snprintf(reply, CLI_REPLY_SIZE, "OK - radio.rxgain=%d", _prefs->rx_boost);
} else {
strcpy(reply, "Error: unsupported");
}
} else {
strcpy(reply, "Error: must be 0, 1, on, or off");
}
+3
View File
@@ -35,6 +35,9 @@ public:
virtual void eraseLogFile() = 0;
virtual void dumpLogFile() = 0;
virtual void setTxPower(int8_t power_dbm) = 0;
/* Apply RX boosted gain live; returns false when the radio has no
* RX boost feature (upstream PR #2844 semantics). */
virtual bool setRxBoostedGain(bool enable) { (void)enable; return false; }
/* Repeater-specific — default replies keep companion builds clean.
* Repeater overrides all four; companions get "not available". */
virtual void formatNeighborsReply(char* reply) { strcpy(reply, "not available"); }
+2 -1
View File
@@ -21,7 +21,8 @@ struct GroupChannel {
class MeshTables {
public:
virtual bool hasSeen(const Packet *packet) = 0;
virtual bool wasSeen(const Packet *packet) = 0; /* pure query, no insert */
virtual void markSeen(const Packet *packet) = 0; /* explicit insert */
virtual void clear(const Packet *packet) = 0;
};
+7 -2
View File
@@ -24,7 +24,7 @@ public:
_direct_dups = _flood_dups = 0;
}
bool hasSeen(const Packet *packet) override {
bool wasSeen(const Packet *packet) override {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
const uint8_t *sp = _hashes;
@@ -38,9 +38,14 @@ public:
return true;
}
}
return false;
}
void markSeen(const Packet *packet) override {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
memcpy(&_hashes[_next_idx * MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES;
return false;
}
void clear(const Packet *packet) override {
+6
View File
@@ -431,6 +431,12 @@ void Dispatcher::checkSend()
len += outbound->payload_len;
uint32_t max_airtime = _radio->getEstAirtimeFor(len) * 3 / 2;
/* Short packets (ACKs) have est airtimes small enough that
* IRQ/work-queue latency alone can blow the watchdog and clip
* the TX mid-air (upstream 4f8cb8db: 200ms est floor, x1.5). */
if (max_airtime < 300) {
max_airtime = 300;
}
outbound_start = now;
#if IS_ENABLED(CONFIG_ZEPHCORE_PACKET_LOGGING)
+27 -16
View File
@@ -146,7 +146,8 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet *pkt)
tmp.path_len = Packet::copyPath(tmp.path, pkt->path, MAX_PATH_SIZE, pkt->path_len);
tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
if (!_tables->hasSeen(&tmp)) {
if (!_tables->wasSeen(&tmp)) {
_tables->markSeen(&tmp);
removeSelfFromPath(&tmp);
routeDirectRecvAcks(&tmp, ((uint32_t)remaining + 1) * 300);
}
@@ -205,7 +206,8 @@ DispatcherAction Mesh::onRecvPacket(Packet *pkt)
uint16_t offset = (uint16_t)pkt->path_len << path_sz;
if (offset >= len) {
onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len);
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
pkt->path[pkt->path_len++] = (int8_t)(pkt->getSNR() * 4);
uint32_t d = getDirectRetransmitDelay(pkt);
return ACTION_RETRANSMIT_DELAYED(5, d);
@@ -241,13 +243,15 @@ DispatcherAction Mesh::onRecvPacket(Packet *pkt)
return forwardMultipartDirect(pkt);
}
if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) {
if (!_tables->hasSeen(pkt)) {
if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
removeSelfFromPath(pkt);
routeDirectRecvAcks(pkt, 0);
}
return ACTION_RELEASE;
}
if (!_tables->hasSeen(pkt)) {
if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
removeSelfFromPath(pkt);
return ACTION_RETRANSMIT_DELAYED(0, getDirectRetransmitDelay(pkt));
}
@@ -282,7 +286,8 @@ DispatcherAction Mesh::onRecvPacket(Packet *pkt)
} else {
uint32_t ack_crc;
memcpy(&ack_crc, pkt->payload, 4);
if (!_tables->hasSeen(pkt)) {
if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
onAckRecv(pkt, ack_crc);
action = routeRecvPacket(pkt);
}
@@ -300,7 +305,8 @@ DispatcherAction Mesh::onRecvPacket(Packet *pkt)
uint8_t *macAndData = &pkt->payload[i];
if (i + CIPHER_MAC_SIZE >= (int)pkt->payload_len) {
LOG_WRN("onRecvPacket: incomplete packet (i=%d, payload_len=%d)", i, pkt->payload_len);
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
if (self_id.isHashMatch(&dest_hash)) {
int num = searchPeersByHash(&src_hash);
bool found = false;
@@ -360,7 +366,8 @@ DispatcherAction Mesh::onRecvPacket(Packet *pkt)
uint8_t *macAndData = &pkt->payload[i];
if (i + 2 >= (int)pkt->payload_len) {
// incomplete packet
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
if (self_id.isHashMatch(&dest_hash)) {
Identity sender(sender_pub_key);
uint8_t secret[PUB_KEY_SIZE];
@@ -385,7 +392,8 @@ DispatcherAction Mesh::onRecvPacket(Packet *pkt)
uint8_t *macAndData = &pkt->payload[i];
if (i + 2 >= (int)pkt->payload_len) {
// incomplete packet
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
GroupChannel channels[4];
int num = searchChannelsByHash(&channel_hash, channels, 4);
for (int j = 0; j < num; j++) {
@@ -410,7 +418,8 @@ DispatcherAction Mesh::onRecvPacket(Packet *pkt)
i += 4;
const uint8_t *signature = &pkt->payload[i];
i += SIGNATURE_SIZE;
if (i <= (int)pkt->payload_len && !self_id.matches(id.pub_key) && !_tables->hasSeen(pkt)) {
if (i <= (int)pkt->payload_len && !self_id.matches(id.pub_key) && !_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
uint8_t *app_data = (uint8_t *)&pkt->payload[i];
size_t app_data_len = pkt->payload_len - (size_t)i;
if (app_data_len > MAX_ADVERT_DATA_SIZE) app_data_len = MAX_ADVERT_DATA_SIZE;
@@ -427,7 +436,8 @@ DispatcherAction Mesh::onRecvPacket(Packet *pkt)
break;
}
case PAYLOAD_TYPE_RAW_CUSTOM:
if (pkt->isRouteDirect() && !_tables->hasSeen(pkt)) {
if (pkt->isRouteDirect() && !_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
onRawDataRecv(pkt);
}
break;
@@ -444,7 +454,8 @@ DispatcherAction Mesh::onRecvPacket(Packet *pkt)
tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
if (!_tables->hasSeen(&tmp)) {
if (!_tables->wasSeen(&tmp)) {
_tables->markSeen(&tmp);
uint32_t ack_crc;
memcpy(&ack_crc, tmp.payload, 4);
onAckRecv(&tmp, ack_crc);
@@ -538,7 +549,7 @@ void Mesh::sendFlood(Packet *packet, uint32_t delay_millis, uint8_t path_hash_si
packet->header &= ~PH_ROUTE_MASK;
packet->header |= ROUTE_TYPE_FLOOD;
packet->setPathHashSizeAndCount(path_hash_size, 0);
_tables->hasSeen(packet);
_tables->markSeen(packet); /* mark as already sent in case it is rebroadcast back to us */
#ifdef CONFIG_ZEPHCORE_APC
{
uint32_t h = ContentionTracker::computePacketHash32(packet);
@@ -573,7 +584,7 @@ void Mesh::sendFlood(Packet *packet, uint16_t *transport_codes, uint32_t delay_m
packet->transport_codes[0] = transport_codes[0];
packet->transport_codes[1] = transport_codes[1];
packet->setPathHashSizeAndCount(path_hash_size, 0);
_tables->hasSeen(packet);
_tables->markSeen(packet); /* mark as already sent in case it is rebroadcast back to us */
#ifdef CONFIG_ZEPHCORE_APC
{
uint32_t h = ContentionTracker::computePacketHash32(packet);
@@ -625,7 +636,7 @@ void Mesh::sendDirect(Packet *packet, const uint8_t *path, uint8_t path_len, uin
}
}
_tables->hasSeen(packet);
_tables->markSeen(packet); /* mark as already sent in case it is rebroadcast back to us */
sendPacket(packet, pri, delay_millis);
}
@@ -634,7 +645,7 @@ void Mesh::sendZeroHop(Packet *packet, uint32_t delay_millis)
packet->header &= ~PH_ROUTE_MASK;
packet->header |= ROUTE_TYPE_DIRECT;
packet->path_len = 0;
_tables->hasSeen(packet);
_tables->markSeen(packet); /* mark as already sent in case it is rebroadcast back to us */
sendPacket(packet, 0, delay_millis);
}
@@ -645,7 +656,7 @@ void Mesh::sendZeroHop(Packet *packet, uint16_t *transport_codes, uint32_t delay
packet->transport_codes[0] = transport_codes[0];
packet->transport_codes[1] = transport_codes[1];
packet->path_len = 0;
_tables->hasSeen(packet);
_tables->markSeen(packet); /* mark as already sent in case it is rebroadcast back to us */
sendPacket(packet, 0, delay_millis);
}
+4
View File
@@ -682,6 +682,10 @@ public:
LOG_INF("TX power %d dBm requested (reboot to apply)", power_dbm);
}
bool setRxBoostedGain(bool enable) override {
return lora_radio.setRxBoost(enable);
}
mesh::LocalIdentity& getSelfId() override { return companion_mesh.self_id; }
void saveIdentity(const mesh::LocalIdentity& new_id) override {