diff --git a/src/helpers/CLICommandUtils.h b/src/helpers/CLICommandUtils.h index 83a1bf6ee..b8206d90f 100644 --- a/src/helpers/CLICommandUtils.h +++ b/src/helpers/CLICommandUtils.h @@ -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, diff --git a/src/helpers/MorseCLI.h b/src/helpers/MorseCLI.h index 4c01dae69..9962eb5e8 100644 --- a/src/helpers/MorseCLI.h +++ b/src/helpers/MorseCLI.h @@ -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 | listen | 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 "); + cli::formatError(reply, capacity, "use morse listen "); 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; diff --git a/src/helpers/MorseRx.h b/src/helpers/MorseRx.h index 6a5ecb724..859646be3 100644 --- a/src/helpers/MorseRx.h +++ b/src/helpers/MorseRx.h @@ -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; diff --git a/src/helpers/MorseTx.h b/src/helpers/MorseTx.h index 33c9aeacf..d7b71234f 100644 --- a/src/helpers/MorseTx.h +++ b/src/helpers/MorseTx.h @@ -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; diff --git a/src/helpers/RadioProfileCLI.cpp b/src/helpers/RadioProfileCLI.cpp index 5c4ba93bf..e63a191ac 100644 --- a/src/helpers/RadioProfileCLI.cpp +++ b/src/helpers/RadioProfileCLI.cpp @@ -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; }