mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-26 16:27:57 +00:00
434 lines
12 KiB
C++
434 lines
12 KiB
C++
#include "CommonRadioPrefs.h"
|
|
#include "TxtDataHelpers.h"
|
|
#include "target.h"
|
|
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
namespace {
|
|
|
|
constexpr size_t RADIO_ARGS_CAPACITY = 132;
|
|
|
|
size_t boundedLength(const char* text, size_t capacity) {
|
|
if (text == nullptr) return capacity;
|
|
size_t length = 0;
|
|
while (length < capacity && text[length] != 0) length++;
|
|
return length;
|
|
}
|
|
|
|
bool startsWith(const char* text, const char* prefix) {
|
|
if (text == nullptr || prefix == nullptr) return false;
|
|
const size_t prefix_len = strlen(prefix);
|
|
return strncmp(text, prefix, prefix_len) == 0;
|
|
}
|
|
|
|
bool parseIntStrict(const char* text, int32_t min_value, int32_t max_value,
|
|
int32_t& result) {
|
|
if (text == nullptr || *text == 0 || min_value > max_value) return false;
|
|
|
|
bool negative = false;
|
|
if (*text == '-') {
|
|
negative = true;
|
|
text++;
|
|
}
|
|
if (*text == 0) return false;
|
|
|
|
int64_t magnitude = 0;
|
|
while (*text != 0) {
|
|
if (*text < '0' || *text > '9') return false;
|
|
magnitude = magnitude * 10 + (*text++ - '0');
|
|
if (magnitude > 2147483648LL) return false;
|
|
}
|
|
|
|
const int64_t parsed = negative ? -magnitude : magnitude;
|
|
if (parsed < min_value || parsed > max_value) return false;
|
|
result = static_cast<int32_t>(parsed);
|
|
return true;
|
|
}
|
|
|
|
// Accept only a plain fixed-point decimal. Exponents, NaN/Inf, whitespace,
|
|
// and trailing characters are rejected so malformed CLI values never become
|
|
// zero through atoi()/atof() fallback behavior.
|
|
bool parseDecimalStrict(const char* text, float& result) {
|
|
if (text == nullptr || *text == 0) return false;
|
|
|
|
const char* cursor = text;
|
|
bool negative = false;
|
|
if (*cursor == '-') {
|
|
negative = true;
|
|
cursor++;
|
|
}
|
|
|
|
bool saw_digit = false;
|
|
double value = 0.0;
|
|
while (*cursor >= '0' && *cursor <= '9') {
|
|
value = value * 10.0 + (*cursor++ - '0');
|
|
if (!isfinite(value)) return false;
|
|
saw_digit = true;
|
|
}
|
|
|
|
if (*cursor == '.') {
|
|
cursor++;
|
|
double place = 0.1;
|
|
while (*cursor >= '0' && *cursor <= '9') {
|
|
value += (*cursor++ - '0') * place;
|
|
place *= 0.1;
|
|
saw_digit = true;
|
|
}
|
|
}
|
|
|
|
if (!saw_digit || *cursor != 0) return false;
|
|
value = negative ? -value : value;
|
|
if (!isfinite(value) || value > 3.402823466e+38
|
|
|| value < -3.402823466e+38) {
|
|
return false;
|
|
}
|
|
|
|
result = static_cast<float>(value);
|
|
return isfinite(result);
|
|
}
|
|
|
|
bool splitRadioArgs(char* text, const char* parts[4]) {
|
|
if (text == nullptr || *text == 0) return false;
|
|
|
|
size_t count = 0;
|
|
char* field = text;
|
|
while (true) {
|
|
if (*field == 0 || count >= 4) return false;
|
|
parts[count++] = field;
|
|
|
|
char* comma = strchr(field, ',');
|
|
if (comma == nullptr) break;
|
|
*comma = 0;
|
|
field = comma + 1;
|
|
}
|
|
return count == 4;
|
|
}
|
|
|
|
bool bwMatches(float bw, float allowed) {
|
|
return fabsf(bw - allowed) <= 0.001f;
|
|
}
|
|
|
|
bool isValidLoRaBandwidth(float bw) {
|
|
#if defined(USE_LR1110)
|
|
return bwMatches(bw, 62.5f)
|
|
|| bwMatches(bw, 125.0f)
|
|
|| bwMatches(bw, 250.0f)
|
|
|| bwMatches(bw, 500.0f);
|
|
#elif defined(USE_LLCC68) || defined(USE_SX1272)
|
|
return bwMatches(bw, 125.0f)
|
|
|| bwMatches(bw, 250.0f)
|
|
|| bwMatches(bw, 500.0f);
|
|
#else
|
|
return bwMatches(bw, 7.8f)
|
|
|| bwMatches(bw, 10.4f)
|
|
|| bwMatches(bw, 15.6f)
|
|
|| bwMatches(bw, 20.8f)
|
|
|| bwMatches(bw, 31.25f)
|
|
|| bwMatches(bw, 41.7f)
|
|
|| bwMatches(bw, 62.5f)
|
|
|| bwMatches(bw, 125.0f)
|
|
|| bwMatches(bw, 250.0f)
|
|
|| bwMatches(bw, 500.0f);
|
|
#endif
|
|
}
|
|
|
|
bool copyValue(char* destination, size_t capacity, const char* value) {
|
|
if (destination == nullptr || capacity == 0 || value == nullptr) return false;
|
|
const size_t value_len = strlen(value);
|
|
if (value_len >= capacity) {
|
|
destination[0] = 0;
|
|
return false;
|
|
}
|
|
memcpy(destination, value, value_len + 1);
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool CommonRadioPrefs::getByKey(const char* key, char* value, size_t max_len) {
|
|
if (key == nullptr || value == nullptr || max_len == 0) return false;
|
|
|
|
if (strcmp(key, "fem_rxgain") == 0) {
|
|
return copyValue(value, max_len, getFEMRxGain() == 0 ? "0" : "1");
|
|
}
|
|
if (strcmp(key, "fem_txgain") == 0) {
|
|
return copyValue(value, max_len, getFEMTxGain() == 0 ? "0" : "1");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CommonRadioPrefs::setByKey(const char* key, const char* value) {
|
|
if (key == nullptr || value == nullptr
|
|
|| (strcmp(value, "0") != 0 && strcmp(value, "1") != 0)) {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t enabled = strcmp(value, "1") == 0 ? 1 : 0;
|
|
if (strcmp(key, "fem_rxgain") == 0) {
|
|
setFEMRxGain(enabled);
|
|
markDirty();
|
|
return true;
|
|
}
|
|
if (strcmp(key, "fem_txgain") == 0) {
|
|
setFEMTxGain(enabled);
|
|
markDirty();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CommonRadioPrefs::handleCommand(const char* command, uint32_t sender_timestamp, char* reply) {
|
|
(void)sender_timestamp;
|
|
if (command == nullptr || reply == nullptr) return false;
|
|
|
|
if (strcmp(command, "get radio") == 0) {
|
|
char freq[16], bw[16];
|
|
snprintf(freq, sizeof(freq), "%s", StrHelper::ftoa(getFreq()));
|
|
snprintf(bw, sizeof(bw), "%s", StrHelper::ftoa3(getBandwidth()));
|
|
sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)getSpreadFactor(), (uint32_t)getCodingRate());
|
|
return true;
|
|
}
|
|
if (startsWith(command, "set radio ")) {
|
|
const char* args = command + strlen("set radio ");
|
|
const size_t args_len = boundedLength(args, RADIO_ARGS_CAPACITY);
|
|
if (args_len >= RADIO_ARGS_CAPACITY) {
|
|
strcpy(reply, "Error, invalid radio params");
|
|
return true;
|
|
}
|
|
|
|
char tmp[RADIO_ARGS_CAPACITY];
|
|
memcpy(tmp, args, args_len + 1);
|
|
const char *parts[4];
|
|
float freq = 0.0f;
|
|
float bw = 0.0f;
|
|
int32_t sf = 0;
|
|
int32_t cr = 0;
|
|
if (splitRadioArgs(tmp, parts)
|
|
&& parseDecimalStrict(parts[0], freq)
|
|
&& parseDecimalStrict(parts[1], bw)
|
|
&& parseIntStrict(parts[2], 5, 12, sf)
|
|
&& parseIntStrict(parts[3], 5, 8, cr)
|
|
&& freq >= 150.0f && freq <= 2500.0f
|
|
&& isValidLoRaBandwidth(bw)) {
|
|
setSpreadFactor(static_cast<uint8_t>(sf));
|
|
setCodingRate(static_cast<uint8_t>(cr));
|
|
setFreq(freq);
|
|
setBandwidth(bw);
|
|
strcpy(reply, "OK - reboot to apply");
|
|
} 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 (startsWith(command, "set af ")) {
|
|
float factor = 0.0f;
|
|
if (parseDecimalStrict(command + strlen("set af "), factor)
|
|
&& factor >= 0.0f && factor <= 9.0f) {
|
|
setAirtimeFactor(factor);
|
|
strcpy(reply, "OK");
|
|
} else {
|
|
strcpy(reply, "Error, invalid airtime factor");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (strcmp(command, "get dutycycle") == 0) {
|
|
const float factor = getAirtimeFactor();
|
|
if (!isfinite(factor) || factor < 0.0f) {
|
|
strcpy(reply, "Error: invalid airtime factor");
|
|
} else {
|
|
const int tenths = static_cast<int>(1000.0f / (factor + 1.0f) + 0.5f);
|
|
sprintf(reply, "> %d.%d%%", tenths / 10, tenths % 10);
|
|
}
|
|
return true;
|
|
}
|
|
if (startsWith(command, "set dutycycle ")) {
|
|
float dc = 0.0f;
|
|
if (!parseDecimalStrict(command + strlen("set dutycycle "), dc)
|
|
|| dc < 1.0f || dc > 100.0f) {
|
|
strcpy(reply, "ERROR: dutycycle must be 1-100");
|
|
} else {
|
|
setAirtimeFactor((100.0f / dc) - 1.0f);
|
|
const float actual = 100.0f / (getAirtimeFactor() + 1.0f);
|
|
const int tenths = static_cast<int>(actual * 10.0f + 0.5f);
|
|
sprintf(reply, "OK - %d.%d%%", tenths / 10, tenths % 10);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (strcmp(command, "get int.thresh") == 0) {
|
|
sprintf(reply, "> %d", (uint32_t) getIntThresh());
|
|
return true;
|
|
}
|
|
if (startsWith(command, "set int.thresh ")) {
|
|
int32_t threshold = 0;
|
|
if (parseIntStrict(command + strlen("set int.thresh "), 0, 255, threshold)) {
|
|
setIntThresh(static_cast<uint8_t>(threshold));
|
|
strcpy(reply, "OK");
|
|
} else {
|
|
strcpy(reply, "Error, must be 0-255");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (strcmp(command, "get cad") == 0) {
|
|
sprintf(reply, "> %s", isCadEnabled() ? "on" : "off");
|
|
return true;
|
|
}
|
|
if (startsWith(command, "set cad ")) {
|
|
const char* value = command + strlen("set cad ");
|
|
if (strcmp(value, "on") == 0 || strcmp(value, "off") == 0) {
|
|
setCadEnabled(strcmp(value, "on") == 0);
|
|
strcpy(reply, "OK");
|
|
} else {
|
|
strcpy(reply, "Error: use set cad on|off");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (strcmp(command, "get radio.rxgain") == 0) {
|
|
sprintf(reply, "> %s", getRxGain() != 0 ? "on" : "off");
|
|
return true;
|
|
}
|
|
if (startsWith(command, "set radio.rxgain ")) {
|
|
const char* value = command + strlen("set radio.rxgain ");
|
|
if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
|
|
strcpy(reply, "Error: use set radio.rxgain on|off");
|
|
return true;
|
|
}
|
|
|
|
const bool enabled = strcmp(value, "on") == 0;
|
|
if (radio_driver.setRxBoostedGainMode(enabled)) {
|
|
setRxGain(enabled ? 1 : 0);
|
|
strcpy(reply, "OK");
|
|
} else {
|
|
strcpy(reply, "Error: unsupported");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (strcmp(command, "get tx") == 0) {
|
|
sprintf(reply, "> %d", static_cast<int>(getTxPower()));
|
|
return true;
|
|
}
|
|
if (startsWith(command, "set tx ")) {
|
|
int32_t dbm = 0;
|
|
if (!parseIntStrict(command + strlen("set tx "), -128, 127, dbm)) {
|
|
strcpy(reply, "Error: invalid TX power");
|
|
} else if (!radio_driver.setTxPower(static_cast<int8_t>(dbm))) {
|
|
strcpy(reply, "Error: TX power rejected");
|
|
} else {
|
|
setTxPower(static_cast<int8_t>(dbm));
|
|
strcpy(reply, "OK");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (strcmp(command, "get rxdelay") == 0) {
|
|
sprintf(reply, "> %s", StrHelper::ftoa(getRxDelay()));
|
|
return true;
|
|
}
|
|
if (startsWith(command, "set rxdelay ")) {
|
|
float db = 0.0f;
|
|
if (parseDecimalStrict(command + strlen("set rxdelay "), db)
|
|
&& db >= 0.0f && 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 (startsWith(command, "set agc.reset.interval ")) {
|
|
int32_t seconds = 0;
|
|
if (parseIntStrict(command + strlen("set agc.reset.interval "), 0, 255, seconds)) {
|
|
setAgcResetInt(static_cast<uint8_t>(seconds));
|
|
sprintf(reply, "OK - interval rounded to %d", (uint32_t) getAgcResetInt());
|
|
} else {
|
|
strcpy(reply, "Error, must be 0-255");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (strcmp(command, "get path.hash.mode") == 0) {
|
|
sprintf(reply, "> %d", (uint32_t)getHashMode());
|
|
return true;
|
|
}
|
|
if (startsWith(command, "set path.hash.mode ")) {
|
|
int32_t mode = 0;
|
|
if (parseIntStrict(command + strlen("set path.hash.mode "), 0, 2, mode)) {
|
|
setHashMode(static_cast<uint8_t>(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 (startsWith(command, "set multi.acks ")) {
|
|
int32_t enabled = 0;
|
|
if (parseIntStrict(command + strlen("set multi.acks "), 0, 1, enabled)) {
|
|
setMultiAcks(static_cast<uint8_t>(enabled));
|
|
strcpy(reply, "OK");
|
|
} else {
|
|
strcpy(reply, "Error, must be 0 or 1");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (strcmp(command, "get txdelay") == 0) {
|
|
sprintf(reply, "> %s", StrHelper::ftoa(getFloodTxDelay()));
|
|
return true;
|
|
}
|
|
if (startsWith(command, "set txdelay ")) {
|
|
float f = 0.0f;
|
|
if (parseDecimalStrict(command + strlen("set txdelay "), f)
|
|
&& f >= 0.0f && 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 (startsWith(command, "set direct.txdelay ")) {
|
|
float f = 0.0f;
|
|
if (parseDecimalStrict(command + strlen("set direct.txdelay "), f)
|
|
&& f >= 0.0f && f <= 2.0f) {
|
|
setDirectTxDelay(f);
|
|
strcpy(reply, "OK");
|
|
} else {
|
|
strcpy(reply, "Error, must be 0-2");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false; // not handled
|
|
}
|