Compare commits

...

23 Commits

Author SHA1 Message Date
Matthias Wientapper
29aa1c3795 rak4631: set lockout voltage to 0V 2026-03-05 16:26:19 +01:00
Matthias Wientapper
38d78041f7 set default: disable flood adverts 2026-03-05 16:26:19 +01:00
Matthias Wientapper
aaa08cb330 Integration of upstrem PR #1810 2026-03-05 16:26:19 +01:00
Matthias Wientapper
15daafff22 Integration of upstrem PR #1338 2026-03-05 16:26:17 +01:00
Matthias Wientapper
8827988842 Integration of upstrem PR #1297 2026-03-05 16:26:16 +01:00
mattzzw
853ee62eb0 Update examples/simple_room_server/MyMesh.cpp
Fix pow() and rand() are calculated for every forwarded packet, but we only need it for flood advert. Also fixes 'paket' typo.

Co-authored-by: Wessel <wessel@weebl.me>
2026-03-05 11:32:47 +01:00
mattzzw
7af6941220 Update examples/simple_repeater/MyMesh.cpp
Fix pow() and rand() are calculated for every forwarded packet, but we only need it for flood advert. Also fixes 'paket' typo.

Co-authored-by: Wessel <wessel@weebl.me>
2026-03-05 11:32:47 +01:00
mattzzw
a678c4a9e6 Update src/helpers/CommonCLI.cpp
Co-authored-by: Wessel <wessel@weebl.me>
2026-03-05 11:32:47 +01:00
Matthias Wientapper
e24b5ff263 Add cli config flood.advert.base
0 = forwarding flood adverts off
1 = forwarding flood adverts on (unrestricted)
0.308 (default) = prob. forwarding according to #1338
2026-03-05 11:32:47 +01:00
Matthias Wientapper
6209ad12ce Limit flood advert packet forwarding for roomservers as well 2026-03-05 11:32:47 +01:00
Matthias Wientapper
971b3f414f Limit flood advert packet forwarding, implements #1223 2026-03-05 11:31:43 +01:00
Scott Powell
5684b4f1b8 * LOOP_DETECT_MODERATE bug fix 2026-03-05 17:38:51 +11:00
Scott Powell
e233346bf0 * repeater: new "get/set loop.detect {off | minimal | moderate | strict }" 2026-03-05 16:26:09 +11:00
Liam Cottle
3031deb980 Merge pull request #1811 from robekl/docs_update_cli_commands
docs: correct CLI command forms and targeted typos
2026-03-05 15:31:32 +13:00
Liam Cottle
dc9e7abacc Merge pull request #1490 from mesher-de/feature-0hop-cli
Add CLI-command for zerohop advert
2026-03-05 15:24:48 +13:00
Liam Cottle
044c66e261 Merge pull request #1001 from kallanreed/fix_gps_debug_logging
Use correct macro for GPS logging test
2026-03-05 13:14:37 +13:00
Robert Ekl
b43319d1a4 docs: correct CLI command forms and targeted typos 2026-02-23 18:53:20 -06:00
Stefan Berthold
f864c5f547 allow direct message paths when denyf * is set 2026-02-23 23:33:41 +01:00
mesher-de
8e404e9aea add advert.zerohop command to CLI 2026-02-17 13:56:18 +01:00
ViezeVingertjes
519b97a90a Updated the Dispatcher logic to replace hardcoded values with defined constants for minimum TX budget reserve and airtime division. 2026-02-07 19:07:33 +01:00
ViezeVingertjes
30d6588792 Update logic in Dispatcher to ensure refill is only applied when greater than zero. 2026-02-07 18:26:39 +01:00
ViezeVingertjes
eb4fa032ff Implement token bucket duty cycle enforcement 2026-01-04 21:33:46 +01:00
kallanreed
9e61b56e70 Use correct macro for logging test 2025-10-22 16:44:20 -07:00
13 changed files with 204 additions and 31 deletions

View File

@@ -51,7 +51,7 @@
- `time <epoch_seconds>` - `time <epoch_seconds>`
**Parameters:** **Parameters:**
- `epoc_seconds`: Unix epoc time - `epoch_seconds`: Unix epoch time
--- ---
@@ -134,7 +134,7 @@
--- ---
### End capture of rx log to node sotrage ### End capture of rx log to node storage
**Usage:** `log stop` **Usage:** `log stop`
--- ---
@@ -198,7 +198,7 @@
**Default:** Varies by board **Default:** Varies by board
**Notes:** This setting only controls the power level of the LoRa chip. Some nodes have an additional power amplifier stage which increases the total output. Referr to the node's manual for the correct setting to use. **Setting a value too high may violate the laws in your country.** **Notes:** This setting only controls the power level of the LoRa chip. Some nodes have an additional power amplifier stage which increases the total output. Refer to the node's manual for the correct setting to use. **Setting a value too high may violate the laws in your country.**
--- ---
@@ -228,6 +228,7 @@
**Default:** `869.525` **Default:** `869.525`
**Note:** Requires reboot to apply **Note:** Requires reboot to apply
**Serial Only:** `set freq <frequency>`
### System ### System
@@ -293,17 +294,16 @@
#### View or change this node's admin password #### View or change this node's admin password
**Usage:** **Usage:**
- `get password` - `password <new_password>`
- `set password <password>`
**Parameters:** **Parameters:**
- `password`: Admin password - `new_password`: New admin password
**Set by build flag:** `ADMIN_PASSWORD` **Set by build flag:** `ADMIN_PASSWORD`
**Default:** `password` **Default:** `password`
**Note:** Echoed back for confirmation **Note:** Command reply echoes the updated password for confirmation.
**Note:** Any node using this password will be added to the admin ACL list. **Note:** Any node using this password will be added to the admin ACL list.
@@ -768,7 +768,7 @@ region save
- `gps advert <policy>` - `gps advert <policy>`
**Parameters:** **Parameters:**
- `policy`: `none`|`shared`|`prefs` - `policy`: `none`|`share`|`prefs`
- `none`: don't include location in adverts - `none`: don't include location in adverts
- `share`: share gps location (from SensorManager) - `share`: share gps location (from SensorManager)
- `prefs`: location stored in node's lat and lon settings - `prefs`: location stored in node's lat and lon settings

View File

@@ -811,7 +811,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 1.0; // one half _prefs.airtime_factor = 1.0;
strcpy(_prefs.node_name, "NONAME"); strcpy(_prefs.node_name, "NONAME");
_prefs.freq = LORA_FREQ; _prefs.freq = LORA_FREQ;
_prefs.sf = LORA_SF; _prefs.sf = LORA_SF;

View File

@@ -396,6 +396,23 @@ File MyMesh::openAppend(const char *fname) {
#endif #endif
} }
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 };
bool MyMesh::isLooped(const mesh::Packet* packet, const uint8_t max_counters[]) {
uint8_t hash_size = packet->getPathHashSize();
uint8_t hash_count = packet->getPathHashCount();
uint8_t n = 0;
const uint8_t* path = packet->path;
while (hash_count > 0) { // count how many times this node is already in the path
if (self_id.isHashMatch(path, hash_size)) n++;
hash_count--;
path += hash_size;
}
return n >= max_counters[hash_size];
}
bool MyMesh::allowPacketForward(const mesh::Packet *packet) { bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
if (_prefs.disable_fwd) return false; if (_prefs.disable_fwd) return false;
if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false; if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false;
@@ -403,6 +420,29 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet");
return false; return false;
} }
if (packet->isRouteFlood() && _prefs.loop_detect != LOOP_DETECT_OFF) {
const uint8_t* maximums;
if (_prefs.loop_detect == LOOP_DETECT_MINIMAL) {
maximums = max_loop_minimal;
} else if (_prefs.loop_detect == LOOP_DETECT_MODERATE) {
maximums = max_loop_moderate;
} else {
maximums = max_loop_strict;
}
if (isLooped(packet, maximums)) {
MESH_DEBUG_PRINTLN("allowPacketForward: FLOOD packet loop detected!");
return false;
}
}
// Limit flood advert paket forwarding using a probabilistic reduction defined by P(h) = 0.308^(hops-1)
// https://github.com/meshcore-dev/MeshCore/issues/1223
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->isRouteFlood()) {
double roll_dice = (double)rand() / RAND_MAX;
double forw_prob = pow(_prefs.flood_advert_base, packet->path_len - 1);
if (roll_dice > forw_prob)
return false;
}
return true; return true;
} }
@@ -506,7 +546,10 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD); recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
if (region_map.getWildcard().flags & REGION_DENY_FLOOD) { if ((pkt->getPayloadType() == PAYLOAD_TYPE_GRP_TXT ||
pkt->getPayloadType() == PAYLOAD_TYPE_GRP_DATA ||
pkt->getPayloadType() == PAYLOAD_TYPE_ADVERT) &&
region_map.getWildcard().flags & REGION_DENY_FLOOD) {
recv_pkt_region = NULL; recv_pkt_region = NULL;
} else { } else {
recv_pkt_region = &region_map.getWildcard(); recv_pkt_region = &region_map.getWildcard();
@@ -824,7 +867,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 1.0; // one half _prefs.airtime_factor = 1.0;
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
_prefs.tx_delay_factor = 0.5f; // was 0.25f _prefs.tx_delay_factor = 0.5f; // was 0.25f
_prefs.direct_tx_delay_factor = 0.3f; // was 0.2 _prefs.direct_tx_delay_factor = 0.3f; // was 0.2
@@ -838,7 +881,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.cr = LORA_CR; _prefs.cr = LORA_CR;
_prefs.tx_power_dbm = LORA_TX_POWER; _prefs.tx_power_dbm = LORA_TX_POWER;
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs _prefs.advert_interval = 1; // default to 2 minutes for NEW installs
_prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_advert_interval = 0; // 12 hours
_prefs.flood_advert_base = 0.308f;
_prefs.flood_max = 64; _prefs.flood_max = 64;
_prefs.interference_threshold = 0; // disabled _prefs.interference_threshold = 0; // disabled

View File

@@ -128,6 +128,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
mesh::Packet* createSelfAdvert(); mesh::Packet* createSelfAdvert();
File openAppend(const char* fname); File openAppend(const char* fname);
bool isLooped(const mesh::Packet* packet, const uint8_t max_counters[]);
protected: protected:
float getAirtimeBudgetFactor() const override { float getAirtimeBudgetFactor() const override {

View File

@@ -276,7 +276,17 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
bool MyMesh::allowPacketForward(const mesh::Packet *packet) { bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
if (_prefs.disable_fwd) return false; if (_prefs.disable_fwd) return false;
if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false; if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false;
// Limit flood advert packet forwarding using a probabilistic reduction defined by P(h) = base^(hops-1)
// https://github.com/meshcore-dev/MeshCore/issues/1223
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->isRouteFlood()) {
double roll_dice = (double)rand() / RAND_MAX;
double forw_prob = pow(_prefs.flood_advert_base, packet->path_len - 1);
if (roll_dice > forw_prob)
return false;
}
return true; return true;
} }
@@ -599,7 +609,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 1.0; // one half _prefs.airtime_factor = 1.0;
_prefs.rx_delay_base = 0.0f; // off by default, was 10.0 _prefs.rx_delay_base = 0.0f; // off by default, was 10.0
_prefs.tx_delay_factor = 0.5f; // was 0.25f; _prefs.tx_delay_factor = 0.5f; // was 0.25f;
_prefs.direct_tx_delay_factor = 0.2f; // was zero _prefs.direct_tx_delay_factor = 0.2f; // was zero
@@ -615,6 +625,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.disable_fwd = 1; _prefs.disable_fwd = 1;
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs _prefs.advert_interval = 1; // default to 2 minutes for NEW installs
_prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_advert_interval = 12; // 12 hours
_prefs.flood_advert_base = 0.308f;
_prefs.flood_max = 64; _prefs.flood_max = 64;
_prefs.interference_threshold = 0; // disabled _prefs.interference_threshold = 0; // disabled
#ifdef ROOM_PASSWORD #ifdef ROOM_PASSWORD

View File

@@ -281,7 +281,7 @@ public:
{ {
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 2.0; // one third _prefs.airtime_factor = 1.0;
strcpy(_prefs.node_name, "NONAME"); strcpy(_prefs.node_name, "NONAME");
_prefs.freq = LORA_FREQ; _prefs.freq = LORA_FREQ;
_prefs.tx_power_dbm = LORA_TX_POWER; _prefs.tx_power_dbm = LORA_TX_POWER;

View File

@@ -706,7 +706,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 1.0; // one half _prefs.airtime_factor = 1.0;
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
_prefs.tx_delay_factor = 0.5f; // was 0.25f _prefs.tx_delay_factor = 0.5f; // was 0.25f
_prefs.direct_tx_delay_factor = 0.2f; // was zero _prefs.direct_tx_delay_factor = 0.2f; // was zero

View File

@@ -8,7 +8,9 @@
namespace mesh { namespace mesh {
#define MAX_RX_DELAY_MILLIS 32000 // 32 seconds #define MAX_RX_DELAY_MILLIS 32000 // 32 seconds
#define MIN_TX_BUDGET_RESERVE_MS 100 // min budget (ms) required before allowing next TX
#define MIN_TX_BUDGET_AIRTIME_DIV 2 // require at least 1/N of estimated airtime as budget before TX
#ifndef NOISE_FLOOR_CALIB_INTERVAL #ifndef NOISE_FLOOR_CALIB_INTERVAL
#define NOISE_FLOOR_CALIB_INTERVAL 2000 // 2 seconds #define NOISE_FLOOR_CALIB_INTERVAL 2000 // 2 seconds
@@ -20,12 +22,34 @@ void Dispatcher::begin() {
_err_flags = 0; _err_flags = 0;
radio_nonrx_start = _ms->getMillis(); radio_nonrx_start = _ms->getMillis();
duty_cycle_window_ms = getDutyCycleWindowMs();
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
tx_budget_ms = (unsigned long)(duty_cycle_window_ms * duty_cycle);
last_budget_update = _ms->getMillis();
_radio->begin(); _radio->begin();
prev_isrecv_mode = _radio->isInRecvMode(); prev_isrecv_mode = _radio->isInRecvMode();
} }
float Dispatcher::getAirtimeBudgetFactor() const { float Dispatcher::getAirtimeBudgetFactor() const {
return 2.0; // default, 33.3% (1/3rd) return 1.0;
}
void Dispatcher::updateTxBudget() {
unsigned long now = _ms->getMillis();
unsigned long elapsed = now - last_budget_update;
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
unsigned long max_budget = (unsigned long)(getDutyCycleWindowMs() * duty_cycle);
unsigned long refill = (unsigned long)(elapsed * duty_cycle);
if (refill > 0) {
tx_budget_ms += refill;
if (tx_budget_ms > max_budget) {
tx_budget_ms = max_budget;
}
last_budget_update = now;
}
} }
int Dispatcher::calcRxDelay(float score, uint32_t air_time) const { int Dispatcher::calcRxDelay(float score, uint32_t air_time) const {
@@ -61,11 +85,24 @@ void Dispatcher::loop() {
if (outbound) { // waiting for outbound send to be completed if (outbound) { // waiting for outbound send to be completed
if (_radio->isSendComplete()) { if (_radio->isSendComplete()) {
long t = _ms->getMillis() - outbound_start; long t = _ms->getMillis() - outbound_start;
total_air_time += t; // keep track of how much air time we are using total_air_time += t;
//Serial.print(" airtime="); Serial.println(t); //Serial.print(" airtime="); Serial.println(t);
// will need radio silence up to next_tx_time updateTxBudget();
next_tx_time = futureMillis(t * getAirtimeBudgetFactor());
if (t > tx_budget_ms) {
tx_budget_ms = 0;
} else {
tx_budget_ms -= t;
}
if (tx_budget_ms < MIN_TX_BUDGET_RESERVE_MS) {
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
unsigned long needed = MIN_TX_BUDGET_RESERVE_MS - tx_budget_ms;
next_tx_time = futureMillis((unsigned long)(needed / duty_cycle));
} else {
next_tx_time = _ms->getMillis();
}
_radio->onSendFinished(); _radio->onSendFinished();
logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len); logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
@@ -235,9 +272,20 @@ void Dispatcher::processRecvPacket(Packet* pkt) {
} }
void Dispatcher::checkSend() { void Dispatcher::checkSend() {
if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return; // nothing waiting to send if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return;
if (!millisHasNowPassed(next_tx_time)) return; // still in 'radio silence' phase (from airtime budget setting)
if (_radio->isReceiving()) { // LBT - check if radio is currently mid-receive, or if channel activity updateTxBudget();
uint32_t est_airtime = _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
if (tx_budget_ms < est_airtime / MIN_TX_BUDGET_AIRTIME_DIV) {
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
unsigned long needed = est_airtime / MIN_TX_BUDGET_AIRTIME_DIV - tx_budget_ms;
next_tx_time = futureMillis((unsigned long)(needed / duty_cycle));
return;
}
if (!millisHasNowPassed(next_tx_time)) return;
if (_radio->isReceiving()) {
if (cad_busy_start == 0) { if (cad_busy_start == 0) {
cad_busy_start = _ms->getMillis(); // record when CAD busy state started cad_busy_start = _ms->getMillis(); // record when CAD busy state started
} }

View File

@@ -122,8 +122,12 @@ class Dispatcher {
bool prev_isrecv_mode; bool prev_isrecv_mode;
uint32_t n_sent_flood, n_sent_direct; uint32_t n_sent_flood, n_sent_direct;
uint32_t n_recv_flood, n_recv_direct; uint32_t n_recv_flood, n_recv_direct;
unsigned long tx_budget_ms;
unsigned long last_budget_update;
unsigned long duty_cycle_window_ms;
void processRecvPacket(Packet* pkt); void processRecvPacket(Packet* pkt);
void updateTxBudget();
protected: protected:
PacketManager* _mgr; PacketManager* _mgr;
@@ -142,6 +146,9 @@ protected:
_err_flags = 0; _err_flags = 0;
radio_nonrx_start = 0; radio_nonrx_start = 0;
prev_isrecv_mode = true; prev_isrecv_mode = true;
tx_budget_ms = 0;
last_budget_update = 0;
duty_cycle_window_ms = 3600000;
} }
virtual DispatcherAction onRecvPacket(Packet* pkt) = 0; virtual DispatcherAction onRecvPacket(Packet* pkt) = 0;
@@ -159,6 +166,7 @@ protected:
virtual uint32_t getCADFailMaxDuration() const; virtual uint32_t getCADFailMaxDuration() const;
virtual int getInterferenceThreshold() const { return 0; } // disabled by default virtual int getInterferenceThreshold() const { return 0; } // disabled by default
virtual int getAGCResetInterval() const { return 0; } // disabled by default virtual int getAGCResetInterval() const { return 0; } // disabled by default
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
public: public:
void begin(); void begin();
@@ -168,8 +176,9 @@ public:
void releasePacket(Packet* packet); void releasePacket(Packet* packet);
void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0); void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0);
unsigned long getTotalAirTime() const { return total_air_time; } // in milliseconds unsigned long getTotalAirTime() const { return total_air_time; }
unsigned long getReceiveAirTime() const {return rx_air_time; } unsigned long getReceiveAirTime() const {return rx_air_time; }
unsigned long getRemainingTxBudget() const { return tx_budget_ms; }
uint32_t getNumSentFlood() const { return n_sent_flood; } uint32_t getNumSentFlood() const { return n_sent_flood; }
uint32_t getNumSentDirect() const { return n_sent_direct; } uint32_t getNumSentDirect() const { return n_sent_direct; }
uint32_t getNumRecvFlood() const { return n_recv_flood; } uint32_t getNumRecvFlood() const { return n_recv_flood; }

View File

@@ -64,7 +64,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116 file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
file.read((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121 file.read((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
file.read(pad, 2); // 122 file.read((uint8_t *)&_prefs->loop_detect, sizeof(_prefs->loop_detect)); // 122
file.read(pad, 1); // 123
file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
@@ -82,7 +83,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
// 290 file.read((uint8_t *)&_prefs->flood_advert_base, sizeof(_prefs->flood_advert_base)); // 290
// 294
// sanitise bad pref values // sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@@ -110,6 +113,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1); _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2); _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
_prefs->flood_advert_base = constrain(_prefs->flood_advert_base, 0, 1);
file.close(); file.close();
} }
} }
@@ -150,7 +155,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116 file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121 file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
file.write(pad, 2); // 122 file.write((uint8_t *)&_prefs->loop_detect, sizeof(_prefs->loop_detect)); // 122
file.write(pad, 1); // 123
file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
@@ -168,7 +174,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
// 290 file.write((uint8_t *)&_prefs->flood_advert_base, sizeof(_prefs->flood_advert_base)); // 290
// 294
file.close(); file.close();
} }
@@ -203,6 +211,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
// Reset clock // Reset clock
getRTCClock()->setCurrentTime(1715770351); // 15 May 2024, 8:50pm getRTCClock()->setCurrentTime(1715770351); // 15 May 2024, 8:50pm
_board->reboot(); // doesn't return _board->reboot(); // doesn't return
} else if (memcmp(command, "advert.zerohop", 14) == 0 && (command[14] == 0 || command[14] == ' ')) {
// send zerohop advert
_callbacks->sendSelfAdvertisement(1500, false); // longer delay, give CLI response time to be sent first
strcpy(reply, "OK - zerohop advert sent");
} else if (memcmp(command, "advert", 6) == 0) { } else if (memcmp(command, "advert", 6) == 0) {
// send flood advert // send flood advert
_callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first _callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first
@@ -330,6 +342,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
*reply = 0; // set null terminator *reply = 0; // set null terminator
} else if (memcmp(config, "path.hash.mode", 14) == 0) { } else if (memcmp(config, "path.hash.mode", 14) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode); sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode);
} else if (memcmp(config, "loop.detect", 11) == 0) {
if (_prefs->loop_detect == LOOP_DETECT_OFF) {
strcpy(reply, "> off");
} else if (_prefs->loop_detect == LOOP_DETECT_MINIMAL) {
strcpy(reply, "> minimal");
} else if (_prefs->loop_detect == LOOP_DETECT_MODERATE) {
strcpy(reply, "> moderate");
} else {
strcpy(reply, "> strict");
}
} else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm); sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm);
} else if (memcmp(config, "freq", 4) == 0) { } else if (memcmp(config, "freq", 4) == 0) {
@@ -385,6 +407,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
} else { } else {
sprintf(reply, "> %.3f", adc_mult); sprintf(reply, "> %.3f", adc_mult);
} }
} else if (memcmp(config, "flood.advert.base", 17) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->flood_advert_base));
// Power management commands // Power management commands
} else if (memcmp(config, "pwrmgt.support", 14) == 0) { } else if (memcmp(config, "pwrmgt.support", 14) == 0) {
#ifdef NRF52_POWER_MANAGEMENT #ifdef NRF52_POWER_MANAGEMENT
@@ -571,6 +595,26 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
} else { } else {
strcpy(reply, "Error, must be 0,1, or 2"); strcpy(reply, "Error, must be 0,1, or 2");
} }
} else if (memcmp(config, "loop.detect ", 12) == 0) {
config += 12;
uint8_t mode;
if (memcmp(config, "off", 3) == 0) {
mode = LOOP_DETECT_OFF;
} else if (memcmp(config, "minimal", 7) == 0) {
mode = LOOP_DETECT_MINIMAL;
} else if (memcmp(config, "moderate", 8) == 0) {
mode = LOOP_DETECT_MODERATE;
} else if (memcmp(config, "strict", 6) == 0) {
mode = LOOP_DETECT_STRICT;
} else {
mode = 0xFF;
strcpy(reply, "Error, must be: off, minimal, moderate, or strict");
}
if (mode != 0xFF) {
_prefs->loop_detect = mode;
savePrefs();
strcpy(reply, "OK");
}
} else if (memcmp(config, "tx ", 3) == 0) { } else if (memcmp(config, "tx ", 3) == 0) {
_prefs->tx_power_dbm = atoi(&config[3]); _prefs->tx_power_dbm = atoi(&config[3]);
savePrefs(); savePrefs();
@@ -642,6 +686,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
_prefs->adc_multiplier = 0.0f; _prefs->adc_multiplier = 0.0f;
strcpy(reply, "Error: unsupported by this board"); strcpy(reply, "Error: unsupported by this board");
}; };
} else if (memcmp(config, "flood.advert.base ", 18) == 0) {
float f = atof(&config[18]);
if(f >= 0 && f <= 1) {
_prefs->flood_advert_base = f;
savePrefs();
strcpy(reply, "OK");
} else {
strcpy(reply, "Error: base must be between 0 and 1");
}
} else { } else {
sprintf(reply, "unknown config: %s", config); sprintf(reply, "unknown config: %s", config);
} }

View File

@@ -13,6 +13,11 @@
#define ADVERT_LOC_SHARE 1 #define ADVERT_LOC_SHARE 1
#define ADVERT_LOC_PREFS 2 #define ADVERT_LOC_PREFS 2
#define LOOP_DETECT_OFF 0
#define LOOP_DETECT_MINIMAL 1
#define LOOP_DETECT_MODERATE 2
#define LOOP_DETECT_STRICT 3
struct NodePrefs { // persisted to file struct NodePrefs { // persisted to file
float airtime_factor; float airtime_factor;
char node_name[32]; char node_name[32];
@@ -36,6 +41,7 @@ struct NodePrefs { // persisted to file
uint8_t flood_max; uint8_t flood_max;
uint8_t interference_threshold; uint8_t interference_threshold;
uint8_t agc_reset_interval; // secs / 4 uint8_t agc_reset_interval; // secs / 4
float flood_advert_base;
// Bridge settings // Bridge settings
uint8_t bridge_enabled; // boolean uint8_t bridge_enabled; // boolean
uint16_t bridge_delay; // milliseconds (default 500 ms) uint16_t bridge_delay; // milliseconds (default 500 ms)
@@ -53,6 +59,7 @@ struct NodePrefs { // persisted to file
float adc_multiplier; float adc_multiplier;
char owner_info[120]; char owner_info[120];
uint8_t path_hash_mode; // which path mode to use when sending uint8_t path_hash_mode; // which path mode to use when sending
uint8_t loop_detect;
}; };
class CommonCLICallbacks { class CommonCLICallbacks {

View File

@@ -683,7 +683,7 @@ void EnvironmentSensorManager::start_gps() {
_location->begin(); _location->begin();
_location->reset(); _location->reset();
#ifndef PIN_GPS_RESET #ifndef PIN_GPS_EN
MESH_DEBUG_PRINTLN("Start GPS is N/A on this board. Actual GPS state unchanged"); MESH_DEBUG_PRINTLN("Start GPS is N/A on this board. Actual GPS state unchanged");
#endif #endif
} }

View File

@@ -106,7 +106,7 @@ extern "C"
// Power management boot protection threshold (millivolts) // Power management boot protection threshold (millivolts)
// Set to 0 to disable boot protection // Set to 0 to disable boot protection
#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) #define PWRMGT_VOLTAGE_BOOTLOCK 0 // Won't boot below this voltage (mV)
// LPCOMP wake configuration (voltage recovery from SYSTEMOFF) // LPCOMP wake configuration (voltage recovery from SYSTEMOFF)
// AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ // AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ
#define PWRMGT_LPCOMP_AIN 3 #define PWRMGT_LPCOMP_AIN 3