Просмотр исходного кода

Merge pull request #1297 from ViezeVingertjes/feature/duty-cycle-token-bucket

Implement token bucket duty cycle enforcement
ripplebiz 5 месяцев назад
Родитель
Сommit
cf0cc851d4

+ 1 - 1
examples/companion_radio/MyMesh.cpp

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

+ 1 - 1
examples/simple_repeater/MyMesh.cpp

@@ -855,7 +855,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
 
   // defaults
   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.tx_delay_factor = 0.5f; // was 0.25f
   _prefs.direct_tx_delay_factor = 0.3f; // was 0.2

+ 1 - 1
examples/simple_room_server/MyMesh.cpp

@@ -599,7 +599,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
 
   // defaults
   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.tx_delay_factor = 0.5f; // was 0.25f;
   _prefs.direct_tx_delay_factor = 0.2f; // was zero

+ 1 - 1
examples/simple_secure_chat/main.cpp

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

+ 1 - 1
examples/simple_sensor/SensorMesh.cpp

@@ -706,7 +706,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
 
   // defaults
   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.tx_delay_factor = 0.5f;   // was 0.25f
   _prefs.direct_tx_delay_factor = 0.2f; // was zero

+ 56 - 8
src/Dispatcher.cpp

@@ -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
     }

+ 10 - 1
src/Dispatcher.h

@@ -123,8 +123,12 @@ class Dispatcher {
   bool  prev_isrecv_mode;
   uint32_t n_sent_flood, n_sent_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 updateTxBudget();
 
 protected:
   PacketManager* _mgr;
@@ -143,6 +147,9 @@ protected:
     _err_flags = 0;
     radio_nonrx_start = 0;
     prev_isrecv_mode = true;
+    tx_budget_ms = 0;
+    last_budget_update = 0;
+    duty_cycle_window_ms = 3600000;
   }
 
   virtual DispatcherAction onRecvPacket(Packet* pkt) = 0;
@@ -160,6 +167,7 @@ protected:
   virtual uint32_t getCADFailMaxDuration() const;
   virtual int getInterferenceThreshold() const { return 0; }    // disabled by default
   virtual int getAGCResetInterval() const { return 0; }    // disabled by default
+  virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
 
 public:
   void begin();
@@ -169,8 +177,9 @@ public:
   void releasePacket(Packet* packet);
   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 getRemainingTxBudget() const { return tx_budget_ms; }
   uint32_t getNumSentFlood() const { return n_sent_flood; }
   uint32_t getNumSentDirect() const { return n_sent_direct; }
   uint32_t getNumRecvFlood() const { return n_recv_flood; }