#include "CommonRadioPrefs.h" #include "TxtDataHelpers.h" #include "Utils.h" #include "target.h" // PowerSaving variant #include "radiolib/RXPowerSavingCLI.h" static RxPowerSavingControl* rxpsControl() { #ifdef WRAPPER_CLASS return &radio_driver; #else return nullptr; #endif } // PowerSaving variant bool CommonRadioPrefs::applyRxPowerSaving(uint8_t sf, float bw) { _active_sf = sf; _active_bw = bw; RxPowerSavingConfig* config = getRxPowerSavingConfig(); if (!config) return false; // Temporary radio parameters must not replace the persisted automatic periods. RxPowerSavingConfig active = *config; return applyRxPowerSavingConfig(&active, sf, bw, rxpsControl()); } // PowerSaving variant bool CommonRadioPrefs::isRxPowerSavingCalibrationActive() const { const auto* control = rxpsControl(); return control && control->isRxPowerSavingCalibrationActive(); } bool CommonRadioPrefs::getByKey(const char* key, char* value, size_t max_len) { if (strcmp(key, "fem_rxgain") == 0) { snprintf(value, max_len, "%d", (uint32_t)getFEMRxGain()); return true; } if (strcmp(key, "fem_txgain") == 0) { snprintf(value, max_len, "%d", (uint32_t)getFEMTxGain()); return true; } return false; } bool CommonRadioPrefs::setByKey(const char* key, const char* value) { if (strcmp(key, "fem_rxgain") == 0) { setFEMRxGain(atoi(value)); markDirty(); return true; } if (strcmp(key, "fem_txgain") == 0) { setFEMTxGain(atoi(value)); markDirty(); return true; } return false; } bool CommonRadioPrefs::handleCommand(const char* command, uint32_t sender_timestamp, char* reply) { // PowerSaving variant if (strncmp(command, "get radio.rxps", 14) == 0 && (command[14] == 0 || command[14] == ' ')) { const char* suffix = command + 14; while (*suffix == ' ') ++suffix; if (*suffix) return false; auto* config = getRxPowerSavingConfig(); if (!config) { strcpy(reply, "Error: unsupported"); return true; } const uint8_t sf = _active_sf ? _active_sf : getSpreadFactor(); const float bw = _active_sf ? _active_bw : getBandwidth(); RxPowerSavingConfig active = *config; normalizeRxPowerSavingConfig(&active, sf, bw, rxPowerSavingCaptureCost(rxpsControl()), rxPowerSavingTransition(rxpsControl())); RXPowerSavingCLI::get(&active, rxpsControl(), sf, bw, reply, 160); return true; } if (strncmp(command, "set radio.rxps ", 15) == 0) { auto* config = getRxPowerSavingConfig(); if (!config) { strcpy(reply, "Error: unsupported"); return true; } if (RXPowerSavingCLI::set(command + 15, _active_sf ? _active_sf : getSpreadFactor(), _active_sf ? _active_bw : getBandwidth(), config, rxpsControl(), reply, 160)) markDirty(); return true; } if (strcmp(command, "get radio") == 0) { char freq[16], bw[16]; strcpy(freq, StrHelper::ftoa(getFreq())); strcpy(bw, StrHelper::ftoa3(getBandwidth())); sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)getSpreadFactor(), (uint32_t)getCodingRate()); return true; } if (memcmp(command, "set radio ", 10) == 0) { char tmp[132]; strcpy(tmp, &command[10]); const char *parts[4]; int num = mesh::Utils::parseTextParts(tmp, parts, 4); float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f; float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; if (freq >= 150.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { setSpreadFactor(sf); setCodingRate(cr); setFreq(freq); setBandwidth(bw); // PowerSaving variant auto* rxps = getRxPowerSavingConfig(); const bool timing_ok = !rxps || !rxps->enabled || normalizeRxPowerSavingConfig( rxps, sf, bw, rxPowerSavingCaptureCost(rxpsControl()), rxPowerSavingTransition(rxpsControl())); if (!timing_ok && rxpsControl()) { rxpsControl()->setRxPowerSaving(false, rxps->rx_us, rxps->sleep_us); rxpsControl()->setRxPowerSavingConfigError(true); } strcpy(reply, timing_ok ? "OK - reboot to apply" : "OK - reboot to apply; RXPS continuous: invalid timing"); } else { strcpy(reply, "Error, invalid radio params"); } return true; } if (strcmp(command, "get freq") == 0) { sprintf(reply, "> %s", StrHelper::ftoa(getFreq())); return true; } if (strcmp(command, "get af") == 0) { sprintf(reply, "> %s", StrHelper::ftoa(getAirtimeFactor())); return true; } if (memcmp(command, "set af ", 7) == 0) { char* end; float af = strtof(&command[7], &end); if (end == &command[7] || af < 0 || af > 9) { strcpy(reply, "ERROR: af must be 0-9"); } else { setAirtimeFactor(af); strcpy(reply, "OK"); } return true; } if (strcmp(command, "get dutycycle") == 0) { float dc = 100.0f / (getAirtimeFactor() + 1.0f); int dc_int = (int)dc; int dc_frac = (int)((dc - dc_int) * 10.0f + 0.5f); sprintf(reply, "> %d.%d%%", dc_int, dc_frac); return true; } if (memcmp(command, "set dutycycle ", 14) == 0) { float dc = atof(&command[14]); if (dc < 1 || dc > 100) { strcpy(reply, "ERROR: dutycycle must be 1-100"); } else { setAirtimeFactor((100.0f / dc) - 1.0f); float actual = 100.0f / (getAirtimeFactor() + 1.0f); int a_int = (int)actual; int a_frac = (int)((actual - a_int) * 10.0f + 0.5f); sprintf(reply, "OK - %d.%d%%", a_int, a_frac); } return true; } if (strcmp(command, "get int.thresh") == 0) { sprintf(reply, "> %d", (uint32_t) getIntThresh()); return true; } if (memcmp(command, "set int.thresh ", 15) == 0) { setIntThresh(atoi(&command[15])); strcpy(reply, "OK"); return true; } if (strcmp(command, "get cad") == 0) { // PowerSaving variant sprintf(reply, "> %s; channel busy: %lu", isCadEnabled() ? "on" : "off", (unsigned long)radio_driver.getCADBusyCount()); return true; } if (memcmp(command, "set cad ", 8) == 0) { setCadEnabled(memcmp(&command[8], "on", 2) == 0); strcpy(reply, "OK"); return true; } if (strcmp(command, "get radio.rxgain") == 0) { sprintf(reply, "> %s", getRxGain() != 0 ? "on" : "off"); return true; } if (memcmp(command, "set radio.rxgain ", 17) == 0) { bool enabled = memcmp(&command[17], "on", 2) == 0; setRxGain(enabled); if (radio_driver.setRxBoostedGainMode(enabled)) { strcpy(reply, "OK"); } else { strcpy(reply, "Error: unsupported"); } return true; } if (memcmp(command, "get tx", 6) == 0 && (command[6] == 0 || command[6] == ' ')) { sprintf(reply, "> %d", (int32_t) getTxPower()); return true; } if (memcmp(command, "set tx ", 7) == 0) { setTxPower(atoi(&command[7])); radio_driver.setTxPower(getTxPower()); strcpy(reply, "OK"); return true; } if (strcmp(command, "get rxdelay") == 0) { sprintf(reply, "> %s", StrHelper::ftoa(getRxDelay())); return true; } if (memcmp(command, "set rxdelay ", 12) == 0) { float db = atof(&command[12]); if (db >= 0 && db <= 20.0f) { setRxDelay(db); strcpy(reply, "OK"); } else { strcpy(reply, "Error, must be 0-20"); } return true; } if (strcmp(command, "get agc.reset.interval") == 0) { sprintf(reply, "> %d", (uint32_t) getAgcResetInt()); return true; } if (memcmp(command, "set agc.reset.interval ", 23) == 0) { int secs = atoi(&command[23]); secs = constrain(secs, 0, 255 * 4); setAgcResetInt((uint16_t) secs); sprintf(reply, "OK - interval rounded to %d", (uint32_t) getAgcResetInt()); return true; } if (strcmp(command, "get path.hash.mode") == 0) { sprintf(reply, "> %d", (uint32_t)getHashMode()); return true; } if (memcmp(command, "set path.hash.mode ", 19) == 0) { const char* config = command + 19; uint8_t mode = atoi(config); if (mode < 3) { setHashMode(mode); strcpy(reply, "OK"); } else { strcpy(reply, "Error, must be 0,1, or 2"); } return true; } if (strcmp(command, "get multi.acks") == 0) { sprintf(reply, "> %d", (uint32_t) getMultiAcks()); return true; } if (memcmp(command, "set multi.acks ", 15) == 0) { setMultiAcks(atoi(&command[15])); strcpy(reply, "OK"); return true; } if (strcmp(command, "get txdelay") == 0) { sprintf(reply, "> %s", StrHelper::ftoa(getFloodTxDelay())); return true; } if (memcmp(command, "set txdelay ", 12) == 0) { float f = atof(&command[12]); if (f >= 0 && f <= 2.0f) { setFloodTxDelay(f); strcpy(reply, "OK"); } else { strcpy(reply, "Error, must be 0-2"); } return true; } if (strcmp(command, "get direct.txdelay") == 0) { sprintf(reply, "> %s", StrHelper::ftoa(getDirectTxDelay())); return true; } if (memcmp(command, "set direct.txdelay ", 19) == 0) { float f = atof(&command[19]); if (f >= 0 && f <= 2.0f) { setDirectTxDelay(f); strcpy(reply, "OK"); } else { strcpy(reply, "Error, must be 0-2"); } return true; } return false; // not handled }