mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-29 16:28:11 +00:00
feat(repeater): suppress Adaptive Power Control while relaying
A repeater wants full, consistent TX power for relay reach, and APC's feedback (own ACKs / own flood echoes) never fires on forwarded traffic — so leaving APC on would relay at whatever reduced power it last trimmed to, or sit inert on a dedicated repeater network. Force it off whenever client_repeat is on, mirroring the power-save lock: - apcActive() = tx_apc && !client_repeat gates every APC control site (sample/failure/track). - applyApc() pins power to the tx_power_dbm ceiling; called at boot, on the on-device repeater toggle, and on the app's CMD_SET_RADIO_PARAMS. - Settings "Auto pwr" shows "--" and blocks the toggle while repeating; the user's pref is preserved and restored when the repeater is switched off. Also refreshes the FEATURES repeater entry (Hold-Enter reset, Errors row, schema 0x10, the radio-setting locks and status indicator). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -496,7 +496,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
||||
// positive feedback on the repeater link — sample its SNR. This is the only
|
||||
// confirmation a channel send gets (no ACK), and is what lets APC recover power
|
||||
// after it has trimmed too far for the repeaters to hear.
|
||||
if (_prefs.tx_apc && _apc_flood_pending && packet->payload_len == _apc_flood_len) {
|
||||
if (apcActive() && _apc_flood_pending && packet->payload_len == _apc_flood_len) {
|
||||
uint8_t h[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(h);
|
||||
if (memcmp(h, _apc_flood_hash, MAX_HASH_SIZE) == 0) {
|
||||
@@ -596,7 +596,7 @@ void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, ui
|
||||
}
|
||||
void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
// TODO: have per-channel send_scope
|
||||
if (_prefs.tx_apc) apcTrackFloodSend(pkt); // listen for a repeater echo to drive APC (channels have no ACK)
|
||||
if (apcActive()) apcTrackFloodSend(pkt); // listen for a repeater echo to drive APC (channels have no ACK)
|
||||
trackRelaySend(pkt); // and for the UI "relayed" marker
|
||||
if (send_unscoped) {
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); // app has explicitly requested un-scoped
|
||||
@@ -1241,7 +1241,7 @@ void MyMesh::trackRelaySend(const mesh::Packet* pkt) {
|
||||
}
|
||||
|
||||
void MyMesh::onSendTimeout() {
|
||||
if (_prefs.tx_apc) apcOnFailure();
|
||||
if (apcActive()) apcOnFailure();
|
||||
}
|
||||
|
||||
void MyMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
||||
@@ -1250,7 +1250,7 @@ void MyMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
||||
// Capture the match before the base handler's processAck() clears the entry.
|
||||
bool mine = isAckPending(ack_crc);
|
||||
BaseChatMesh::onAckRecv(packet, ack_crc);
|
||||
if (mine && _prefs.tx_apc) apcSampleSnr(radio_driver.getLastSNR());
|
||||
if (mine && apcActive()) apcSampleSnr(radio_driver.getLastSNR());
|
||||
// Drive the DM delivery-status marker. Note isAckPending() only covers
|
||||
// app/serial-initiated sends (expected_ack_table); a DM composed on the
|
||||
// device UI registers in BaseChatMesh's own ack table instead, so gating on
|
||||
@@ -1861,9 +1861,11 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
savePrefs();
|
||||
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
// Keep the "repeating ⇒ continuous RX" invariant when repeat is toggled via
|
||||
// the app, mirroring the on-device path (a repeater must hear all traffic).
|
||||
// Keep the "repeating ⇒ continuous RX, full TX power" invariants when repeat
|
||||
// is toggled via the app, mirroring the on-device path (a repeater must hear
|
||||
// all traffic and relay at consistent power).
|
||||
radio_driver.setPowerSaving(_prefs.rx_powersave && !_prefs.client_repeat);
|
||||
applyApc(); // pins power to the ceiling; apcActive() keeps it there while repeating
|
||||
MESH_DEBUG_PRINTLN("OK: CMD_SET_RADIO_PARAMS: f=%d, bw=%d, sf=%d, cr=%d", freq, bw, (uint32_t)sf,
|
||||
(uint32_t)cr);
|
||||
|
||||
@@ -2774,7 +2776,7 @@ void MyMesh::loop() {
|
||||
// treated as a lost confirmation → ramp power up (lets channel sends recover).
|
||||
if (_apc_flood_pending && millisHasNowPassed(_apc_flood_deadline)) {
|
||||
_apc_flood_pending = false;
|
||||
if (_prefs.tx_apc) apcOnFailure();
|
||||
if (apcActive()) apcOnFailure();
|
||||
}
|
||||
// UI relay windows expired with no echo — just drop them (no echo is not a
|
||||
// failure for channels; the marker simply stays "sent").
|
||||
|
||||
@@ -156,6 +156,11 @@ protected:
|
||||
// Overhear suppression only makes sense while repeating; gated behind its own
|
||||
// opt-in pref (Settings > Radio > Suppress dup).
|
||||
bool wantsOverhearSuppress() const override { return _prefs.client_repeat && _prefs.repeat_suppress_dup; }
|
||||
// Adaptive Power Control is suppressed while repeating: a repeater wants full,
|
||||
// consistent TX power for relay reach, and its feedback sources (own ACKs /
|
||||
// own flood echoes) don't fire on forwarded traffic anyway. applyApc() then
|
||||
// pins power to the ceiling.
|
||||
bool apcActive() const { return _prefs.tx_apc && !_prefs.client_repeat; }
|
||||
|
||||
void sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis);
|
||||
void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override;
|
||||
|
||||
@@ -260,6 +260,7 @@ public:
|
||||
p->client_repeat ^= 1;
|
||||
the_mesh.applyRepeaterRadio(); // switch to profile on enable / restore companion on disable
|
||||
_task->applyPowerSave(); // duty-cycle RX is forced off while repeating
|
||||
_task->applyApc(); // pin TX power to the ceiling while repeating (APC suppressed)
|
||||
buildItems(p);
|
||||
_dirty = true;
|
||||
return true;
|
||||
|
||||
@@ -613,7 +613,9 @@ class SettingsScreen : public UIScreen {
|
||||
} else if (item == TX_APC) {
|
||||
display.print("Auto pwr");
|
||||
display.setCursor(valCol(display), y);
|
||||
display.print((p && p->tx_apc) ? "ON" : "OFF");
|
||||
// Suppressed (and locked) while repeating — a repeater holds full TX power.
|
||||
if (p && p->client_repeat) display.print("--");
|
||||
else display.print((p && p->tx_apc) ? "ON" : "OFF");
|
||||
#if AUTO_OFF_MILLIS > 0
|
||||
} else if (item == AUTO_OFF) {
|
||||
display.print("AutoOff");
|
||||
@@ -1007,6 +1009,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
if (_selected == TX_APC && p && (left || right || enter)) {
|
||||
if (p->client_repeat) { _task->showAlert("Off while repeating", 900); return true; }
|
||||
p->tx_apc ^= 1;
|
||||
_task->applyApc();
|
||||
_dirty = true;
|
||||
|
||||
Reference in New Issue
Block a user