diff --git a/src/airtime.cpp b/src/airtime.cpp index fed4ef8aa..1c2fb3233 100644 --- a/src/airtime.cpp +++ b/src/airtime.cpp @@ -117,6 +117,20 @@ float AirTime::utilizationTXPercent() return (float(sum) / float(MS_IN_HOUR)) * 100; } +// Get the amount of minutes we have to be silent before we can send again +uint8_t AirTime::getSilentMinutes(float txPercent, float dutyCycle) +{ + float newTxPercent = txPercent; + for (int8_t i = MINUTES_IN_HOUR-1; i >= 0; --i) { + newTxPercent -= ((float)this->utilizationTX[i] / (MS_IN_MINUTE * MINUTES_IN_HOUR / 100)); + if (newTxPercent < dutyCycle) + return MINUTES_IN_HOUR-1-i; + } + + return MINUTES_IN_HOUR; +} + + AirTime::AirTime() : concurrency::OSThread("AirTime"),airtimes({}) { } diff --git a/src/airtime.h b/src/airtime.h index f6b9bdcb5..3f38f39f8 100644 --- a/src/airtime.h +++ b/src/airtime.h @@ -29,6 +29,7 @@ #define PERIODS_TO_LOG 8 #define MINUTES_IN_HOUR 60 #define SECONDS_IN_MINUTE 60 +#define MS_IN_MINUTE (SECONDS_IN_MINUTE * 1000) #define MS_IN_HOUR (MINUTES_IN_HOUR * SECONDS_IN_MINUTE * 1000) @@ -57,6 +58,7 @@ class AirTime : private concurrency::OSThread uint32_t getSecondsPerPeriod(); uint32_t getSecondsSinceBoot(); uint32_t *airtimeReport(reportTypes reportType); + uint8_t getSilentMinutes(float txPercent, float dutyCycle); private: bool firstTime = true; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6b2d5bfb3..c30542def 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -162,6 +162,7 @@ void NodeDB::installDefaultConfig() config.has_network = true; config.has_bluetooth = true; config.lora.tx_enabled = true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off) + config.lora.override_duty_cycle = false; config.lora.region = Config_LoRaConfig_RegionCode_UNSET; config.lora.modem_preset = Config_LoRaConfig_ModemPreset_LONG_FAST; config.lora.hop_limit = HOP_RELIABLE; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 6b6a03a3a..5ce26f49a 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -2,6 +2,7 @@ #include "Channels.h" #include "CryptoEngine.h" #include "NodeDB.h" +#include "MeshRadio.h" #include "RTC.h" #include "configuration.h" #include "main.h" @@ -187,6 +188,18 @@ ErrorCode Router::send(MeshPacket *p) { assert(p->to != nodeDB.getNodeNum()); // should have already been handled by sendLocal + // Abort sending if we are violating the duty cycle + if (!config.lora.override_duty_cycle && myRegion->dutyCycle != 100) { + float hourlyTxPercent = airTime->utilizationTXPercent(); + if (hourlyTxPercent > myRegion->dutyCycle) { + uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle); + DEBUG_MSG("WARNING: Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.\n", silentMinutes); + Routing_Error err = Routing_Error_DUTY_CYCLE_LIMIT; + abortSendAndNak(err, p); + return err; + } + } + // PacketId nakId = p->decoded.which_ackVariant == SubPacket_fail_id_tag ? p->decoded.ackVariant.fail_id : 0; // assert(!nakId); // I don't think we ever send 0hop naks over the wire (other than to the phone), test that assumption with // assert diff --git a/src/mesh/generated/apponly.pb.h b/src/mesh/generated/apponly.pb.h index 63f3cf3bf..e5e933873 100644 --- a/src/mesh/generated/apponly.pb.h +++ b/src/mesh/generated/apponly.pb.h @@ -54,7 +54,7 @@ extern const pb_msgdesc_t ChannelSet_msg; #define ChannelSet_fields &ChannelSet_msg /* Maximum encoded size of messages (where known) */ -#define ChannelSet_size 582 +#define ChannelSet_size 584 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/config.pb.h b/src/mesh/generated/config.pb.h index 2d6f3a1d9..cdc89664c 100644 --- a/src/mesh/generated/config.pb.h +++ b/src/mesh/generated/config.pb.h @@ -126,6 +126,7 @@ typedef struct _Config_LoRaConfig { bool tx_enabled; int8_t tx_power; uint16_t channel_num; + bool override_duty_cycle; pb_size_t ignore_incoming_count; uint32_t ignore_incoming[3]; } Config_LoRaConfig; @@ -235,7 +236,7 @@ extern "C" { #define Config_NetworkConfig_init_default {0, "", "", "", 0, _Config_NetworkConfig_EthMode_MIN, false, Config_NetworkConfig_IpV4Config_init_default} #define Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define Config_DisplayConfig_init_default {0, _Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _Config_DisplayConfig_DisplayUnits_MIN, _Config_DisplayConfig_OledType_MIN} -#define Config_LoRaConfig_init_default {0, _Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, {0, 0, 0}} +#define Config_LoRaConfig_init_default {0, _Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, {0, 0, 0}} #define Config_BluetoothConfig_init_default {0, _Config_BluetoothConfig_PairingMode_MIN, 0} #define Config_init_zero {0, {Config_DeviceConfig_init_zero}} #define Config_DeviceConfig_init_zero {_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0} @@ -244,7 +245,7 @@ extern "C" { #define Config_NetworkConfig_init_zero {0, "", "", "", 0, _Config_NetworkConfig_EthMode_MIN, false, Config_NetworkConfig_IpV4Config_init_zero} #define Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define Config_DisplayConfig_init_zero {0, _Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _Config_DisplayConfig_DisplayUnits_MIN, _Config_DisplayConfig_OledType_MIN} -#define Config_LoRaConfig_init_zero {0, _Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, {0, 0, 0}} +#define Config_LoRaConfig_init_zero {0, _Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, {0, 0, 0}} #define Config_BluetoothConfig_init_zero {0, _Config_BluetoothConfig_PairingMode_MIN, 0} /* Field tags (for use in manual encoding/decoding) */ @@ -274,6 +275,7 @@ extern "C" { #define Config_LoRaConfig_tx_enabled_tag 9 #define Config_LoRaConfig_tx_power_tag 10 #define Config_LoRaConfig_channel_num_tag 11 +#define Config_LoRaConfig_override_duty_cycle_tag 12 #define Config_LoRaConfig_ignore_incoming_tag 103 #define Config_NetworkConfig_IpV4Config_ip_tag 1 #define Config_NetworkConfig_IpV4Config_gateway_tag 2 @@ -407,6 +409,7 @@ X(a, STATIC, SINGULAR, UINT32, hop_limit, 8) \ X(a, STATIC, SINGULAR, BOOL, tx_enabled, 9) \ X(a, STATIC, SINGULAR, INT32, tx_power, 10) \ X(a, STATIC, SINGULAR, UINT32, channel_num, 11) \ +X(a, STATIC, SINGULAR, BOOL, override_duty_cycle, 12) \ X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) #define Config_LoRaConfig_CALLBACK NULL #define Config_LoRaConfig_DEFAULT NULL @@ -443,7 +446,7 @@ extern const pb_msgdesc_t Config_BluetoothConfig_msg; #define Config_BluetoothConfig_size 10 #define Config_DeviceConfig_size 18 #define Config_DisplayConfig_size 22 -#define Config_LoRaConfig_size 68 +#define Config_LoRaConfig_size 70 #define Config_NetworkConfig_IpV4Config_size 20 #define Config_NetworkConfig_size 161 #define Config_PositionConfig_size 42 diff --git a/src/mesh/generated/localonly.pb.h b/src/mesh/generated/localonly.pb.h index 054cae4db..01baddc2f 100644 --- a/src/mesh/generated/localonly.pb.h +++ b/src/mesh/generated/localonly.pb.h @@ -150,7 +150,7 @@ extern const pb_msgdesc_t LocalModuleConfig_msg; #define LocalModuleConfig_fields &LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define LocalConfig_size 385 +#define LocalConfig_size 387 #define LocalModuleConfig_size 361 #ifdef __cplusplus diff --git a/src/mesh/generated/mesh.pb.h b/src/mesh/generated/mesh.pb.h index 8d96960d1..f22ec6b12 100644 --- a/src/mesh/generated/mesh.pb.h +++ b/src/mesh/generated/mesh.pb.h @@ -185,9 +185,11 @@ typedef enum _Routing_Error { /* TODO: REPLACE */ Routing_Error_NO_RESPONSE = 8, /* TODO: REPLACE */ - Routing_Error_BAD_REQUEST = 32, + Routing_Error_DUTY_CYCLE_LIMIT = 9, /* The new version of the heltec WiFi_Lora_32_V2 board that has battery sensing hooked to GPIO 37. Sadly they did not update anything on the silkscreen to identify this board */ + Routing_Error_BAD_REQUEST = 32, + /* Ancient heltec WiFi_Lora_32 board */ Routing_Error_NOT_AUTHORIZED = 33 } Routing_Error; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 3ce8731e7..434a42972 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -192,6 +192,7 @@ bool AdminModule::handleReceivedProtobuf(const MeshPacket &mp, AdminMessage *r) void AdminModule::handleSetOwner(const User &o) { int changed = 0; + bool licensed_changed = false; if (*o.long_name) { changed |= strcmp(owner.long_name, o.long_name); @@ -207,12 +208,14 @@ void AdminModule::handleSetOwner(const User &o) } if (owner.is_licensed != o.is_licensed) { changed = 1; + licensed_changed = true; owner.is_licensed = o.is_licensed; + config.lora.override_duty_cycle = owner.is_licensed; // override duty cycle for licensed operators } if (changed) { // If nothing really changed, don't broadcast on the network or write to flash service.reloadOwner(!hasOpenEditTransaction); - saveChanges(SEGMENT_DEVICESTATE); + licensed_changed ? saveChanges(SEGMENT_CONFIG | SEGMENT_DEVICESTATE) : saveChanges(SEGMENT_DEVICESTATE); } }