refactor: share Morse and CLI encoding to preserve full firmware headroom

This commit is contained in:
mikecarper committed 2026-10-20 16:52:48 -07:00
1 parent 099c5a851a
commit ea5920506f
5 files changed
+128 -106

No files matched your search

+6
View File
@@ -8,6 +8,12 @@
namespace mesh {
namespace cli {
// Share fixed error prefixes without duplicating their bytes in each message.
// Formatting keeps the same termination and truncation as other CLI replies.
inline void formatError(char* reply, size_t capacity, const char* message) {
snprintf(reply, capacity, "Error: %s", message);
}
enum class StandaloneWiFiKey : uint8_t {
None = 0,
SSID,
+74 -60
View File
@@ -9,6 +9,20 @@
namespace mesh {
enum class MorseAction : uint8_t { None, Stop, Send, Listen, Read, Clear, Stats, Trace };
inline MorseAction parseMorseAction(const char* word, size_t length) {
static const char entries[][7] = {"stop", "send", "listen", "read", "clear", "stats"
#if defined(MESH_MORSE_HIL) && MESH_MORSE_HIL
, "trace"
#endif
};
if (length > 6) return MorseAction::None;
for (unsigned i = 0; i < sizeof(entries) / sizeof(entries[0]); ++i)
if (!entries[i][length] && !memcmp(word, entries[i], length)) return MorseAction(i + 1);
return MorseAction::None;
}
inline const char* morseUsageError() {
#if MESH_ENABLE_MORSE_RX
return "Error: use morse send <text> | listen <minutes> | read [offset] | stats | clear | stop";
@@ -55,11 +69,10 @@ inline bool handleMorseReplyCommand(MorseReplyHandoff& handoff, Radio* radio,
const bool base = key_length == 5 && !strncmp(key, "morse", 5);
const char* action = cli::skipRecentRepeaterSpaces(key + key_length);
const size_t action_length = strcspn(action, " \t");
const auto action_code = parseMorseAction(action, action_length);
const char* argument = cli::skipRecentRepeaterSpaces(action + action_length);
const bool stop = base && !query && !setting && action_length == 4
&& !strncmp(action, "stop", 4) && !*argument;
const bool listen = base && !query && !setting && action_length == 6
&& !strncmp(action, "listen", 6);
const bool stop = base && !query && !setting && action_code == MorseAction::Stop && !*argument;
const bool listen = base && !query && !setting && action_code == MorseAction::Listen;
if (stop && handoff.pending()) {
handoff.cancel();
snprintf(reply, capacity, "OK - pending Morse RX cancelled; packet radio unchanged");
@@ -72,10 +85,10 @@ inline bool handleMorseReplyCommand(MorseReplyHandoff& handoff, Radio* radio,
}
// Prevent a second explicit RF/settings action from replacing the staged
// request. Read-only capture/status queries remain available.
const bool read_only_morse = base && ((action_length == 4 && !strncmp(action, "read", 4))
|| (action_length == 5 && !strncmp(action, "stats", 5))
const bool read_only_morse = base && ((action_code == MorseAction::Read)
|| (action_code == MorseAction::Stats)
#if defined(MESH_MORSE_HIL) && MESH_MORSE_HIL
|| (action_length == 5 && !strncmp(action, "trace", 5))
|| (action_code == MorseAction::Trace)
#endif
);
const bool cw = (key_length == 2 && !strncmp(key, "cw", 2))
@@ -84,7 +97,7 @@ inline bool handleMorseReplyCommand(MorseReplyHandoff& handoff, Radio* radio,
|| (!query && cw && *action) || !strncmp(key, "tempradio", 9)
|| (!query && key_length == 5 && !strncmp(key, "radio", 5) && *action)
|| (!query && key_length == 6 && !strncmp(key, "radio2", 6) && *action)) {
snprintf(reply, capacity, "Error: Morse reply handoff pending; use morse stop or retry later");
cli::formatError(reply, capacity, "Morse reply handoff pending; use morse stop or retry later");
return true;
}
}
@@ -95,12 +108,12 @@ inline bool handleMorseReplyCommand(MorseReplyHandoff& handoff, Radio* radio,
const uint32_t duration = uint32_t(minutes * 60000.0f + 0.5f);
if (duration < 1000 || duration > 300000) return false;
if (radio->isCarrierWaveActive() || radio->isMorseTxActive() || radio->isMorseRxActive()) {
snprintf(reply, capacity, "Error: radio busy; stop Morse/CW or retry when idle");
cli::formatError(reply, capacity, "radio busy; stop Morse/CW or retry when idle");
return true;
}
const auto& settings = morseSettings();
if (!handoff.stage(settings.frequency_khz, settings.wpm, duration, now)) {
snprintf(reply, capacity, "Error: Morse reply handoff pending; retry later");
cli::formatError(reply, capacity, "Morse reply handoff pending; retry later");
return true;
}
snprintf(reply, capacity,
@@ -118,7 +131,7 @@ inline void formatMorseCapturePage(Radio* radio, const char* argument,
char* reply, size_t capacity) {
uint32_t offset = 0;
if (*argument && !cli::parseUnsignedIntegerStrict(argument, offset)) {
snprintf(reply, capacity, "Error: use morse read [character offset]");
cli::formatError(reply, capacity, "use morse read [character offset]");
return;
}
const size_t total = radio->morseCaptureLength();
@@ -131,7 +144,7 @@ inline void formatMorseCapturePage(Radio* radio, const char* argument,
const int prefix = snprintf(reply, capacity, "> %04u/%04u next=---- truncated=%u ",
unsigned(offset), unsigned(total), unsigned(radio->morseCaptureTruncated()));
if (prefix < 0 || size_t(prefix) + 1 >= capacity) {
snprintf(reply, capacity, "Error: reply buffer too small for capture page");
cli::formatError(reply, capacity, "reply buffer too small for capture page");
return;
}
const size_t copied = radio->readMorseCapture(offset, reply + prefix, capacity - prefix);
@@ -148,7 +161,7 @@ inline void formatMorseTracePage(Radio* radio, const char* argument,
char* reply, size_t capacity) {
uint32_t offset = 0;
if (*argument && !cli::parseUnsignedIntegerStrict(argument, offset)) {
snprintf(reply, capacity, "Error: use morse trace [retained entry offset]");
cli::formatError(reply, capacity, "use morse trace [retained entry offset]");
return;
}
const size_t length = radio->morseTraceLength();
@@ -162,14 +175,14 @@ inline void formatMorseTracePage(Radio* radio, const char* argument,
unsigned(offset), unsigned(length), (unsigned long)radio->morseTraceTotal(),
unsigned(radio->morseTraceTruncated()));
if (prefix < 0 || size_t(prefix) + 1 >= capacity) {
snprintf(reply, capacity, "Error: reply buffer too small for trace page");
cli::formatError(reply, capacity, "reply buffer too small for trace page");
return;
}
size_t used = prefix, next = offset;
for (unsigned count = 0; count < 8 && next < length; ++count) {
MorseRssiTraceEntry entry{};
if (!radio->readMorseTrace(next, entry)) {
snprintf(reply, capacity, "Error: trace changed; retry from offset 0");
cli::formatError(reply, capacity, "trace changed; retry from offset 0");
return;
}
char row[32];
@@ -182,7 +195,7 @@ inline void formatMorseTracePage(Radio* radio, const char* argument,
++next;
}
if (next == offset && next < length) {
snprintf(reply, capacity, "Error: reply buffer too small for trace entry");
cli::formatError(reply, capacity, "reply buffer too small for trace entry");
return;
}
char following[4];
@@ -221,7 +234,7 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
&& !radio->supportsMorseRx()
#endif
)) {
snprintf(reply, capacity, "Error: Morse unsupported on this radio");
cli::formatError(reply, capacity, "Morse unsupported on this radio");
return true;
}
const char* args = cli::skipRecentRepeaterSpaces(key + length);
@@ -229,40 +242,40 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
if (hil_squelch) {
#if MESH_ENABLE_MORSE_RX
if (!radio->supportsMorseRx()) {
snprintf(reply, capacity, "Error: Morse RX unsupported on this radio");
cli::formatError(reply, capacity, "Morse RX unsupported on this radio");
return true;
}
if (query) {
if (*args) snprintf(reply, capacity, "Error: unexpected argument");
if (*args) cli::formatError(reply, capacity, "unexpected argument");
else if (isnan(morseHilSquelch())) snprintf(reply, capacity, "> auto; volatile HIL RX only");
else snprintf(reply, capacity, "> %.3f dBm; volatile HIL RX only", morseHilSquelch());
return true;
}
if (!setting) {
snprintf(reply, capacity, "Error: use get/set morse.hil.squelch auto|-160..0");
cli::formatError(reply, capacity, "use get/set morse.hil.squelch auto|-160..0");
return true;
}
if (radio->isCarrierWaveActive() || radio->isMorseTxActive() || radio->isMorseRxActive()
|| !radio->isInRecvMode() || radio->isReceiving()) {
snprintf(reply, capacity, "Error: radio busy; HIL squelch changes require idle RX");
cli::formatError(reply, capacity, "radio busy; HIL squelch changes require idle RX");
return true;
}
float value = NAN;
if ((strcmp(args, "auto") && (!cli::parseDecimalStrict(args, value)
|| !isfinite(value))) || !setMorseHilSquelch(value)) {
snprintf(reply, capacity, "Error: use morse.hil.squelch auto or -160..0 dBm");
cli::formatError(reply, capacity, "use morse.hil.squelch auto or -160..0 dBm");
return true;
}
if (isnan(value)) snprintf(reply, capacity, "OK - HIL Morse squelch auto; volatile RX only");
else snprintf(reply, capacity, "OK - HIL Morse squelch %.3f dBm; volatile RX only", value);
#else
snprintf(reply, capacity, "Error: Morse RX unsupported on this radio");
cli::formatError(reply, capacity, "Morse RX unsupported on this radio");
#endif
return true;
}
if (hil_timing) {
if (query) {
if (*args) snprintf(reply, capacity, "Error: unexpected argument");
if (*args) cli::formatError(reply, capacity, "unexpected argument");
else {
const auto& timing = morseHilTiming();
snprintf(reply, capacity, "> jitter=%u%%; drift=%d%%; extraGap=%u%%; seed=%lu; volatile HIL TX only",
@@ -272,17 +285,17 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
return true;
}
if (!setting) {
snprintf(reply, capacity, "Error: use get/set morse.hil.timing");
cli::formatError(reply, capacity, "use get/set morse.hil.timing");
return true;
}
if (radio->isCarrierWaveActive() || radio->isMorseTxActive() || radio->isMorseRxActive()
|| !radio->isInRecvMode() || radio->isReceiving()) {
snprintf(reply, capacity, "Error: radio busy; HIL timing changes require idle RX");
cli::formatError(reply, capacity, "radio busy; HIL timing changes require idle RX");
return true;
}
MorseHilTimingConfig timing;
if (!parseMorseHilTiming(args, timing) || !setMorseHilTiming(timing)) {
snprintf(reply, capacity, "Error: use jitter0-20,drift-20..20,extraGap0-50,seed1..4294967295");
cli::formatError(reply, capacity, "use jitter0-20,drift-20..20,extraGap0-50,seed1..4294967295");
return true;
}
snprintf(reply, capacity, "OK - HIL Morse timing %u,%d,%u,%lu; volatile TX only",
@@ -293,12 +306,12 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
#endif
const auto& current = morseSettings();
if ((query || (base && !*args && !setting)) && !morseSettingsUsable()) {
snprintf(reply, capacity, "Error: saved Morse settings unavailable; verify and save morse.freq");
cli::formatError(reply, capacity, "saved Morse settings unavailable; verify and save morse.freq");
return true;
}
if (frequency || speed) {
if (query) {
if (*args) snprintf(reply, capacity, "Error: unexpected argument");
if (*args) cli::formatError(reply, capacity, "unexpected argument");
else if (frequency) snprintf(reply, capacity, "> %.3f", current.frequencyMHz());
else snprintf(reply, capacity, "> %u", unsigned(current.wpm));
return true;
@@ -308,7 +321,7 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
return true;
}
if (speed && !morseSettingsUsable()) {
snprintf(reply, capacity, "Error: verify and save morse.freq before changing Morse WPM");
cli::formatError(reply, capacity, "verify and save morse.freq before changing Morse WPM");
return true;
}
if (radio->isCarrierWaveActive() || radio->isMorseTxActive()
@@ -316,7 +329,7 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
|| radio->isMorseRxActive()
#endif
) {
snprintf(reply, capacity, "Error: radio busy; stop Morse/CW before changing settings");
cli::formatError(reply, capacity, "radio busy; stop Morse/CW before changing settings");
return true;
}
auto candidate = current;
@@ -325,7 +338,7 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
if (!cli::parseDecimalStrict(args, value)
|| value < MorseMinFrequencyKHz / 1000.0f
|| value > MorseMaxFrequencyKHz / 1000.0f) {
snprintf(reply, capacity, "Error: morse.freq must be 100-1000 MHz");
cli::formatError(reply, capacity, "morse.freq must be 100-1000 MHz");
return true;
}
candidate.frequency_khz = uint32_t(value * 1000.0f + 0.5f);
@@ -340,7 +353,7 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
candidate.wpm = uint8_t(value);
}
if (!saveMorseSettings(candidate)) {
snprintf(reply, capacity, "Error: Morse settings could not be saved");
cli::formatError(reply, capacity, "Morse settings could not be saved");
} else if (frequency) {
snprintf(reply, capacity, "OK - morse.freq %.3f MHz", candidate.frequencyMHz());
} else snprintf(reply, capacity, "OK - morse.wpm %u", unsigned(candidate.wpm));
@@ -385,15 +398,16 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
return true;
}
const size_t action_length = strcspn(args, " \t");
const auto action_code = parseMorseAction(args, action_length);
const char* text = cli::skipRecentRepeaterSpaces(args + action_length);
const bool stop = action_length == 4 && !strncmp(args, "stop", 4) && !*text;
const bool send = action_length == 4 && !strncmp(args, "send", 4) && *text;
const bool listen = action_length == 6 && !strncmp(args, "listen", 6);
const bool read = action_length == 4 && !strncmp(args, "read", 4);
const bool clear = action_length == 5 && !strncmp(args, "clear", 5) && !*text;
const bool stats = action_length == 5 && !strncmp(args, "stats", 5) && !*text;
const bool stop = action_code == MorseAction::Stop && !*text;
const bool send = action_code == MorseAction::Send && *text;
const bool listen = action_code == MorseAction::Listen;
const bool read = action_code == MorseAction::Read;
const bool clear = action_code == MorseAction::Clear && !*text;
const bool stats = action_code == MorseAction::Stats && !*text;
#if defined(MESH_MORSE_HIL) && MESH_MORSE_HIL
const bool trace = action_length == 5 && !strncmp(args, "trace", 5);
const bool trace = action_code == MorseAction::Trace;
#else
const bool trace = false;
#endif
@@ -406,7 +420,7 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
&& !radio->supportsMorseRx()
#endif
) {
snprintf(reply, capacity, "Error: Morse RX unsupported on this radio");
cli::formatError(reply, capacity, "Morse RX unsupported on this radio");
return true;
}
#if MESH_ENABLE_MORSE_RX
@@ -432,7 +446,7 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
}
if (clear) {
if (radio->isMorseRxActive() || radio->isMorseTxActive())
snprintf(reply, capacity, "Error: stop Morse before clearing capture");
cli::formatError(reply, capacity, "stop Morse before clearing capture");
else { radio->clearMorseCapture(); snprintf(reply, capacity, "OK - Morse capture cleared"); }
return true;
}
@@ -440,7 +454,7 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
const auto result = radio->stopMorseRx();
if (result == MorseRxResult::Stopped) snprintf(reply, capacity,
"OK - Morse RX stopped; capture retained%s", radio->isInRecvMode() ? "; receive resumed" : "");
else snprintf(reply, capacity, "Error: Morse RX stop/receive recovery pending; capture retained");
else cli::formatError(reply, capacity, "Morse RX stop/receive recovery pending; capture retained");
return true;
}
#endif // MESH_ENABLE_MORSE_RX
@@ -450,19 +464,19 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
return true;
}
if ((send || listen) && !morseSettingsUsable()) {
snprintf(reply, capacity, "Error: saved Morse settings unavailable; verify and save morse.freq");
cli::formatError(reply, capacity, "saved Morse settings unavailable; verify and save morse.freq");
return true;
}
#if MESH_ENABLE_MORSE_RX
if (listen) {
float minutes = 0;
if (!cli::parseDecimalStrict(text, minutes) || minutes <= 0 || minutes > 5) {
snprintf(reply, capacity, "Error: use morse listen <minutes: 1 second to 5 minutes>");
cli::formatError(reply, capacity, "use morse listen <minutes: 1 second to 5 minutes>");
return true;
}
const uint32_t duration = uint32_t(minutes * 60000.0f + 0.5f);
if (duration < 1000 || duration > 300000) {
snprintf(reply, capacity, "Error: Morse listening must last 1-300 seconds");
cli::formatError(reply, capacity, "Morse listening must last 1-300 seconds");
return true;
}
const auto result = radio->startMorseRx(current.frequencyMHz(), current.wpm, duration);
@@ -472,25 +486,25 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
current.frequencyMHz(), radio->morseRxBandwidthKHz(), minutes);
break;
case MorseRxResult::Busy:
snprintf(reply, capacity, "Error: radio busy; stop Morse/CW or retry when idle"); break;
cli::formatError(reply, capacity, "radio busy; stop Morse/CW or retry when idle"); break;
case MorseRxResult::Unsupported:
snprintf(reply, capacity, "Error: Morse RX unsupported on this radio"); break;
cli::formatError(reply, capacity, "Morse RX unsupported on this radio"); break;
case MorseRxResult::InvalidFrequency:
snprintf(reply, capacity, "Error: Morse frequency unsupported by this radio"); break;
cli::formatError(reply, capacity, "Morse frequency unsupported by this radio"); break;
case MorseRxResult::Overlap:
snprintf(reply, capacity, "Error: Morse frequency overlaps radio or radio2; change morse.freq"); break;
cli::formatError(reply, capacity, "Morse frequency overlaps radio or radio2; change morse.freq"); break;
case MorseRxResult::InvalidSpeed:
snprintf(reply, capacity, "Error: morse.wpm must be %u-%u", unsigned(MorseMinWpm), unsigned(MorseMaxWpm)); break;
case MorseRxResult::InvalidDuration:
snprintf(reply, capacity, "Error: Morse listening must last 1-300 seconds"); break;
cli::formatError(reply, capacity, "Morse listening must last 1-300 seconds"); break;
default:
snprintf(reply, capacity, "Error: Morse RX transition failed; check radio recovery"); break;
cli::formatError(reply, capacity, "Morse RX transition failed; check radio recovery"); break;
}
return true;
}
#endif
if (send && !radio->supportsMorseTx()) {
snprintf(reply, capacity, "Error: Morse TX unsupported on this radio");
cli::formatError(reply, capacity, "Morse TX unsupported on this radio");
return true;
}
const auto result = stop ? radio->stopMorseTx()
@@ -505,28 +519,28 @@ inline bool handleMorseCommand(Radio* radio, const char* command,
radio->isInRecvMode() ? "; receive resumed" : "");
break;
case MorseTxResult::Busy:
snprintf(reply, capacity, "Error: radio busy; stop Morse/CW or retry when idle");
cli::formatError(reply, capacity, "radio busy; stop Morse/CW or retry when idle");
break;
case MorseTxResult::Unsupported:
snprintf(reply, capacity, "Error: Morse TX unsupported on this radio");
cli::formatError(reply, capacity, "Morse TX unsupported on this radio");
break;
case MorseTxResult::InvalidFrequency:
snprintf(reply, capacity, "Error: Morse frequency unsupported by this radio");
cli::formatError(reply, capacity, "Morse frequency unsupported by this radio");
break;
case MorseTxResult::Overlap:
snprintf(reply, capacity, "Error: Morse frequency overlaps radio or radio2; change morse.freq");
cli::formatError(reply, capacity, "Morse frequency overlaps radio or radio2; change morse.freq");
break;
case MorseTxResult::InvalidText:
snprintf(reply, capacity, "Error: supported Morse ASCII only (80 characters max)");
cli::formatError(reply, capacity, "supported Morse ASCII only (80 characters max)");
break;
case MorseTxResult::InvalidSpeed:
snprintf(reply, capacity, "Error: morse.wpm must be %u-%u", unsigned(MorseMinWpm), unsigned(MorseMaxWpm));
break;
case MorseTxResult::TooLong:
snprintf(reply, capacity, "Error: limit 80 characters/180 seconds; shorten text or raise WPM");
cli::formatError(reply, capacity, "limit 80 characters/180 seconds; shorten text or raise WPM");
break;
default:
snprintf(reply, capacity, "Error: Morse failed; check radio recovery");
cli::formatError(reply, capacity, "Morse failed; check radio recovery");
break;
}
return true;
+1 -8
View File
@@ -435,14 +435,7 @@ private:
void flushLetter() {
if (!_symbols && !_bad_pattern) return;
char decoded = '?';
if (!_bad_pattern) {
for (char c = '!'; c <= 'Z'; ++c) {
const uint8_t code = MorseTx::codeFor(c);
if (code && code == _code) { decoded = c; break; }
}
// The printable loop ends at Z; '_' is also an ITU punctuation code.
if (MorseTx::codeFor('_') == _code) decoded = '_';
}
if (!_bad_pattern) decoded = MorseTx::characterFor(_code);
append(decoded);
_code = 1;
_symbols = 0;
+23 -14
View File
@@ -125,15 +125,14 @@ public:
// A single flash-resident ASCII table avoids a punctuation switch/jump
// table and keeps the complete alphabet on compact targets. Zero entries
// remain unsupported; lowercase still uses its uppercase ITU code.
static const uint8_t codes[] = {
107,82,0,137,0,40,94,54,109,0,42,115,97,85,50, // ! through /
63,47,39,35,33,32,48,56,60,62, // 0 through 9
120,106,0,49,0,76,90, // : through @
5,24,26,12,2,18,14,16,4,23,13,20,7,6,15,22,29,10,8,3,9,17,11,25,27,28,
0,0,0,0,77 // [ through _
};
const uint8_t index = uint8_t(uint8_t(character) - uint8_t('!'));
return index < sizeof(codes) ? codes[index] : 0;
return index < CodeCount ? codeTable()[index] : 0;
}
static char characterFor(uint8_t code) {
for (uint8_t index = 0; index < CodeCount; ++index)
if (code && codeTable()[index] == code) return char('!' + index);
return '?';
}
static uint8_t symbolCount(uint8_t code) {
@@ -157,14 +156,13 @@ public:
for (; length <= MaxTextLength && text[length]; ++length) {
const char character = text[length];
if (character == ' ') { if (have_character) have_space = true; continue; }
const uint8_t code = codeFor(character);
uint8_t code = codeFor(character);
if (!code) return MorseTxResult::InvalidText;
if (have_character) units += have_space ? 7 : 3;
const uint8_t count = symbolCount(code);
for (uint8_t bit = count; bit; --bit) {
units += (code & (1U << (bit - 1))) ? 3 : 1;
if (bit > 1) ++units;
}
// Each symbol contributes its mark plus one unit of following space.
// Remove the final space; the leading sentinel is not a Morse symbol.
do { units += (code & 1U) ? 4 : 2; code >>= 1; } while (code > 1);
--units;
have_character = true;
have_space = false;
}
@@ -294,6 +292,17 @@ public:
}
private:
static constexpr uint8_t CodeCount = '_' - '!' + 1;
static const uint8_t* codeTable() {
static const uint8_t codes[CodeCount] = {
107,82,0,137,0,40,94,54,109,0,42,115,97,85,50, // ! through /
63,47,39,35,33,32,48,56,60,62, // 0 through 9
120,106,0,49,0,76,90, // : through @
5,24,26,12,2,18,14,16,4,23,13,20,7,6,15,22,29,10,8,3,9,17,11,25,27,28,
0,0,0,0,77 // [ through _
};
return codes;
}
char _text[MaxTextLength + 1] = {};
uint32_t _next = 0, _hard_end = 0, _remaining = 0;
uint16_t _unit = 0, _phase_ms = 0;
+24 -24
View File
@@ -792,14 +792,14 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
const bool scan = !strcmp(key, "radio2.scan");
const bool timing = !strcmp(key, "radio2.timing") || !strcmp(key, "radio.timing");
if (!base && !temporary && !scheduled && !crossing && !replies && !status && !scan && !timing) return false;
if (!radio_ || !radio_->profiles()) { snprintf(reply, capacity, "Error: radio profiles unsupported"); return true; }
if (!radio_ || !radio_->profiles()) { cli::formatError(reply, capacity, "radio profiles unsupported"); return true; }
if (command == text && *args && verb == Get && (temporary || base)) verb = Set;
if (remote_origin && !remote_command_
&& (((base || temporary) && verb == Set) || (scheduled_temp && verb == Delete))) {
snprintf(reply, capacity, "Error: this role requires local USB for radio2 changes"); return true;
cli::formatError(reply, capacity, "this role requires local USB for radio2 changes"); return true;
}
if (hasReplyMutation() && verb != Get) {
snprintf(reply, capacity, "Error: radio acknowledgement pending"); return true;
cli::formatError(reply, capacity, "radio acknowledgement pending"); return true;
}
if (replies) {
const auto* p = radio_->profiles();
@@ -820,14 +820,14 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
if (verb != Set || length >= sizeof(mode) || !parseRadioTxPolicy(mode, policy)
|| (*suffix && !force)
|| (force && policy != RADIO_TX_BOTH && policy != RADIO_TX_SECONDARY)) {
snprintf(reply, capacity, "Error: use get/set tx.reply auto|radio|radio2|both|off [force for radio2/both]");
cli::formatError(reply, capacity, "use get/set tx.reply auto|radio|radio2|both|off [force for radio2/both]");
return true;
}
const uint8_t previous = reply_setting_;
reply_setting_ = 0xb0 | policy | (force ? 8 : 0);
if (!save(saved_, primary_preamble_, cross_)) {
reply_setting_ = previous;
snprintf(reply, capacity, "Error: settings could not be saved");
cli::formatError(reply, capacity, "settings could not be saved");
} else {
publish();
snprintf(reply, capacity, "OK - tx.reply=%s%s", radioTxPolicyName(policy), force ? " force" : "");
@@ -835,7 +835,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
return true;
}
if (timing) {
if (verb != Get || *args) { snprintf(reply, capacity, "Error: radio timing is read-only"); return true; }
if (verb != Get || *args) { cli::formatError(reply, capacity, "radio timing is read-only"); return true; }
const auto& p = *radio_->profiles();
const auto t = p.chirpTiming();
if (!p.enabled()) snprintf(reply, capacity, "> off");
@@ -846,7 +846,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
(unsigned long)((p.recommendationRemainingMs(millis()) + 999) / 1000),
(unsigned long)p.switchBudgetUs(),
radio_->profilePreamble(0), radio_->profilePreamble(1));
else if (!t.valid) snprintf(reply, capacity, "Error: timing unavailable");
else if (!t.valid) cli::formatError(reply, capacity, "timing unavailable");
else {
snprintf(reply, capacity, "> chirps=%.2f,%.2f; need=%.0f,%.0f; switch=%luus; loop=%luus%s",
t.listen_us[0] / t.symbol_us[0], t.listen_us[1] / t.symbol_us[1],
@@ -866,12 +866,12 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
} else if (verb == Set && (!strcmp(args, "auto") || !strcmp(args, "on") || !strcmp(args, "off"))) {
auto mode = !strcmp(args, "auto") ? RadioCrossMode::Auto : !strcmp(args, "on") ? RadioCrossMode::On : RadioCrossMode::Off;
if (save(saved_, primary_preamble_, mode)) { cross_ = mode; publish(); snprintf(reply, capacity, "OK"); }
else snprintf(reply, capacity, "Error: settings could not be saved");
} else snprintf(reply, capacity, "Error: use get/set radio2.cross [auto|on|off]");
else cli::formatError(reply, capacity, "settings could not be saved");
} else cli::formatError(reply, capacity, "use get/set radio2.cross [auto|on|off]");
return true;
}
if (scan) {
if (verb != Get || *args) { snprintf(reply, capacity, "Error: radio2.scan is read-only"); return true; }
if (verb != Get || *args) { cli::formatError(reply, capacity, "radio2.scan is read-only"); return true; }
const auto& p = *radio_->profiles();
if (!p.enabled()) snprintf(reply, capacity, "> off");
else {
@@ -885,7 +885,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
return true;
}
if (status) {
if (verb != Get || *args) { snprintf(reply, capacity, "Error: radio2.status is read-only"); return true; }
if (verb != Get || *args) { cli::formatError(reply, capacity, "radio2.status is read-only"); return true; }
if (hasReplyMutation()) {
snprintf(reply, capacity, "> radio2 change pending %s; previous channel retained",
remote_delivered_ ? "storage commit (retrying)" : "reply transmission");
@@ -929,11 +929,11 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
if (remote_command_ || temp_pending_ || temp_active_ || hasSecondarySchedule()
|| hasReplyMutation() || publish_pending_ || radio_->profiles()->primary_temporary
|| radio_->profiles()->secondary_temporary) {
snprintf(reply, capacity, "Error: automatic radio2 requires no pending change, lease or schedule");
cli::formatError(reply, capacity, "automatic radio2 requires no pending change, lease or schedule");
return true;
}
if (!save({}, primary_preamble_, cross_, 0)) {
snprintf(reply, capacity, "Error: settings could not be saved"); return true;
cli::formatError(reply, capacity, "settings could not be saved"); return true;
}
noteSecondaryManualChange();
saved_ = {}; manual_secondary_ = false;
@@ -946,7 +946,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
if ((base || temporary) && verb == Set && !strcmp(args, "off")) {
if (base && !(remote_command_ ? prepareSavedImage(ReplyPath, {}, primary_preamble_, cross_, true)
: save({}, primary_preamble_, cross_, true))) {
snprintf(reply, capacity, "Error: settings could not be saved"); return true;
cli::formatError(reply, capacity, "settings could not be saved"); return true;
}
if (remote_command_) {
stageRemoteMutation(base ? RemoteMutation::Off : RemoteMutation::TempOff);
@@ -965,7 +965,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
if (scheduled && (verb == Get || verb == Delete)) {
uint32_t index = 0;
if (*args && strcmp(args, "all") && (!cli::parseUnsignedIntegerStrict(args, index) || index < 1 || index > 4)) {
snprintf(reply, capacity, "Error: slot must be 1-4 or all"); return true;
cli::formatError(reply, capacity, "slot must be 1-4 or all"); return true;
}
const unsigned first = scheduled_temp ? 4 : 0;
if (verb == Delete) {
@@ -1004,9 +1004,9 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
}
return true;
}
if (verb != Set) { snprintf(reply, capacity, "Error: invalid radio2 command"); return true; }
if (verb != Set) { cli::formatError(reply, capacity, "invalid radio2 command"); return true; }
char local[140];
if (strlen(args) >= sizeof(local)) { snprintf(reply, capacity, "Error: params too long"); return true; }
if (strlen(args) >= sizeof(local)) { cli::formatError(reply, capacity, "params too long"); return true; }
strcpy(local, args);
char* parts[8];
const unsigned count = split(local, parts, 8);
@@ -1033,7 +1033,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
if (scheduled) {
if (!epoch || start <= epoch || (uint64_t)(start-epoch) * 1000 > 0x7fffffffULL
|| (scheduled_temp && (end <= start || (uint64_t)(end-epoch) * 1000 > 0x7fffffffULL))) {
snprintf(reply, capacity, "Error: future UTC start/end required, within 24 days"); return true;
cli::formatError(reply, capacity, "future UTC start/end required, within 24 days"); return true;
}
if (scheduled_temp) {
const uint64_t pending_left = temp_pending_
@@ -1041,10 +1041,10 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
: 0;
const uint64_t session_left = pending_left > temp_remaining_ms_ ? pending_left : temp_remaining_ms_;
if ((temp_active_ || temp_pending_) && (uint64_t)(start-epoch)*1000 < session_left) {
snprintf(reply, capacity, "Error: temporary schedule overlaps active session"); return true;
cli::formatError(reply, capacity, "temporary schedule overlaps active session"); return true;
}
for (const auto& s : schedules_) if (s.active && s.temporary && start < s.end && end > s.start) {
snprintf(reply, capacity, "Error: temporary schedules overlap"); return true;
cli::formatError(reply, capacity, "temporary schedules overlap"); return true;
}
}
const unsigned first = scheduled_temp ? 4 : 0;
@@ -1059,7 +1059,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
#if MESH_ENABLE_FLEET_CONTROL
if (remote_command_) {
if (!stageRemoteSchedule(i, s)) {
snprintf(reply, capacity, "Error: radio acknowledgement pending"); return true;
cli::formatError(reply, capacity, "radio acknowledgement pending"); return true;
}
} else { schedules_[first+i] = s; noteSecondaryManualChange(); publish(); }
#else
@@ -1068,11 +1068,11 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
snprintf(reply, capacity, "OK - %s %u queued, preamble=%u", key, i+1, preview.preamble(1, 32));
appendSecondaryWarnings(reply, capacity, preview); return true;
}
snprintf(reply, capacity, "Error: all four schedule slots occupied"); return true;
cli::formatError(reply, capacity, "all four schedule slots occupied"); return true;
}
if (temporary) {
for (const auto& s : schedules_) if (s.active && s.temporary) {
snprintf(reply, capacity, "Error: clear tempradioat2 schedules first"); return true;
cli::formatError(reply, capacity, "clear tempradioat2 schedules first"); return true;
}
if (remote_command_) stageRemoteMutation(RemoteMutation::Temporary, config, minutes * 60000UL);
else {
@@ -1099,7 +1099,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
}
snprintf(reply, capacity, "OK - radio2 %s%s; preamble=%u", modeName(config.mode),
remote_command_ ? " pending after reply" : "", preview.preamble(1, 32));
} else snprintf(reply, capacity, "Error: settings could not be saved");
} else cli::formatError(reply, capacity, "settings could not be saved");
if (!strncmp(reply, "OK", 2)) appendSecondaryWarnings(reply, capacity, preview);
return true;
}