|
|
@@ -8,7 +8,9 @@
|
|
|
|
|
|
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
|
|
|
#define NOISE_FLOOR_CALIB_INTERVAL 2000 // 2 seconds
|
|
|
@@ -20,12 +22,34 @@ void Dispatcher::begin() {
|
|
|
_err_flags = 0;
|
|
|
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();
|
|
|
prev_isrecv_mode = _radio->isInRecvMode();
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
@@ -61,11 +85,24 @@ void Dispatcher::loop() {
|
|
|
if (outbound) { // waiting for outbound send to be completed
|
|
|
if (_radio->isSendComplete()) {
|
|
|
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);
|
|
|
|
|
|
- // will need radio silence up to next_tx_time
|
|
|
- next_tx_time = futureMillis(t * getAirtimeBudgetFactor());
|
|
|
+ updateTxBudget();
|
|
|
+
|
|
|
+ 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();
|
|
|
logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
|
|
|
@@ -235,9 +272,20 @@ void Dispatcher::processRecvPacket(Packet* pkt) {
|
|
|
}
|
|
|
|
|
|
void Dispatcher::checkSend() {
|
|
|
- if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return; // nothing waiting to send
|
|
|
- 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
|
|
|
+ if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return;
|
|
|
+
|
|
|
+ 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) {
|
|
|
cad_busy_start = _ms->getMillis(); // record when CAD busy state started
|
|
|
}
|