diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 681e431f3..ed9117b7a 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1,3 +1,4 @@ +#include #include "MyMesh.h" #include @@ -234,7 +235,7 @@ static const char FLOOD_PACKET_FILTER_USAGE[] = #if MESH_ENABLE_FLOOD_RULE_ENGINE "Err - use: set flood.rule[.n] type= [hops=] [...]"; #else - "Err - use: set flood.filter[.n] [hops] [path=blacklist] [scope=] [require=region] [tx=slow] [suspend=tempradio]"; + MESH_CLI_REPLY_TEXT("Err - use: set flood.filter[.n] [hops] [path=blacklist] [scope=] [require=region] [tx=slow] [suspend=tempradio]", "Err - set flood.filter[.n] [hops] [path=blacklist] [scope=] [require=region] [tx=slow] [suspend=tempradio]"); #endif static const char FLOOD_PACKET_FILTER_DUPLICATE[] = "Err - duplicate filter option"; @@ -3173,8 +3174,8 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, request_id, packet->radio_profile, packet->radio_generation)) { const char* error = deferred_cli_command.pending - ? "Err - another remote command is still running" - : "Err - remote command is too long"; + ? MESH_CLI_REPLY_TEXT("Err - another remote command is still running", "Err - remote command busy") + : MESH_CLI_REPLY_TEXT("Err - remote command is too long", "Err - remote command too long"); remote_cli_reply_cache.remember(client->id.pub_key, request_id, command_fingerprint, error); sendRemoteCliReply(client, secret, packet->getPathHashSize(), @@ -4926,15 +4927,15 @@ void MyMesh::addScheduledRadioParams(bool temporary, float freq, float bw, uint8 return; } if (start_time <= now) { - strcpy(reply, "Error: start is in the past"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error: start is in the past", "Error: start in past")); return; } if (temporary && end_time <= now) { - strcpy(reply, "Error: end is in the past"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error: end is in the past", "Error: end in past")); return; } if (temporary && end_time <= start_time) { - strcpy(reply, "Error: end must be after start"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error: end must be after start", "Error: end must follow start")); return; } if (countScheduledRadioSettings(temporary) >= MAX_SCHEDULED_RADIO_SETTINGS_PER_TYPE) { @@ -5054,7 +5055,7 @@ void MyMesh::formatScheduledRadioParams(bool temporary, const char* selector, ch int wanted = 0; if (!parsePositiveSelector(selector, wanted)) { - strcpy(reply, temporary ? "Error, use: get tempradioat [n]" : "Error, use: get radioat [n]"); + strcpy(reply, temporary ? MESH_CLI_REPLY_TEXT("Error, use: get tempradioat [n]", "Error, get tempradioat [n]") : MESH_CLI_REPLY_TEXT("Error, use: get radioat [n]", "Error, get radioat [n]")); return; } @@ -5077,7 +5078,7 @@ void MyMesh::deleteScheduledRadioParams(bool temporary, const char* selector, ch if (!all) { int wanted = 0; if (!parsePositiveSelector(selector, wanted)) { - strcpy(reply, temporary ? "Error, use: del tempradioat [n]" : "Error, use: del radioat [n]"); return; + strcpy(reply, temporary ? MESH_CLI_REPLY_TEXT("Error, use: del tempradioat [n]", "Error, del tempradioat [n]") : MESH_CLI_REPLY_TEXT("Error, use: del radioat [n]", "Error, del radioat [n]")); return; } idx = findScheduledRadioSettingByIndex(temporary, wanted); if (idx < 0) { strcpy(reply, "Error: not found"); return; } @@ -5122,7 +5123,7 @@ void MyMesh::deleteScheduledRadioParams(bool temporary, const char* selector, ch int wanted = 0; if (!parsePositiveSelector(selector, wanted)) { - strcpy(reply, temporary ? "Error, use: del tempradioat [n]" : "Error, use: del radioat [n]"); + strcpy(reply, temporary ? MESH_CLI_REPLY_TEXT("Error, use: del tempradioat [n]", "Error, del tempradioat [n]") : MESH_CLI_REPLY_TEXT("Error, use: del radioat [n]", "Error, del radioat [n]")); return; } @@ -5696,7 +5697,7 @@ bool MyMesh::handleGuestCommand(const char* command, char* reply, size_t capacit const char* value = command + 9; while (*value == ' ' || *value == '\t') ++value; if ((get && *value) || (set && strcmp(value, "on") && strcmp(value, "off"))) { - snprintf(reply, capacity, "Err - use get guest or set guest on|off"); + snprintf(reply, capacity, MESH_CLI_REPLY_TEXT("Err - use get guest or set guest on|off", "Err - get guest / set guest on|off")); } else if (get) { snprintf(reply, capacity, "> %s%s", guest_login.enabled() ? "on" : "off", guest_login.healthy() ? "" : " (settings unavailable)"); @@ -6391,7 +6392,7 @@ void MyMesh::formatFloodPacketFilterBlacklist(const char* args, char* reply) con uint8_t slot = 0; if (!parseFloodFilterUnsigned(selector, FLOOD_PACKET_FILTER_BLACKLIST_MAX, slot) || slot == 0 || slot > flood_packet_filter_blacklist_count) { - snprintf(reply, 160, "Err - blacklist slot must be 1-%u", + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - blacklist slot must be 1-%u", "Err - blacklist slot: 1-%u"), (uint32_t)flood_packet_filter_blacklist_count); return; } @@ -6437,7 +6438,7 @@ void MyMesh::setFloodPacketFilterBlacklist(const char* args, char* reply) { size_t slot_len = (size_t)(cursor - slot_start); char slot_text[8]; if (slot_len == 0 || slot_len >= sizeof(slot_text)) { - snprintf(reply, 160, "Err - blacklist slot must be 1-%u", + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - blacklist slot must be 1-%u", "Err - blacklist slot: 1-%u"), (uint32_t)FLOOD_PACKET_FILTER_BLACKLIST_MAX); return; } @@ -6446,13 +6447,13 @@ void MyMesh::setFloodPacketFilterBlacklist(const char* args, char* reply) { uint8_t slot = 0; if (!parseFloodFilterUnsigned(slot_text, FLOOD_PACKET_FILTER_BLACKLIST_MAX, slot) || slot == 0) { - snprintf(reply, 160, "Err - blacklist slot must be 1-%u", + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - blacklist slot must be 1-%u", "Err - blacklist slot: 1-%u"), (uint32_t)FLOOD_PACKET_FILTER_BLACKLIST_MAX); return; } requested_slot = slot - 1; if (*cursor != ' ') { - strcpy(reply, "Err - expected six-digit repeater ID"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - expected six-digit repeater ID", "Err - repeater ID: six hex digits")); return; } } @@ -6463,10 +6464,10 @@ void MyMesh::setFloodPacketFilterBlacklist(const char* args, char* reply) { uint8_t count = 0; if (!parseFloodPacketFilterBlacklist(cursor, parsed, count)) { if (requested_slot >= 0) { - snprintf(reply, 160, "Err - use 1-%d unique six-digit hex IDs separated by commas", + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - use 1-%d unique six-digit hex IDs separated by commas", "Err - use 1-%d unique six-digit hex IDs, comma-separated"), FLOOD_PACKET_FILTER_BLACKLIST_REPLACE_MAX); } else { - snprintf(reply, 160, "Err - use 1-%d unique six-digit hex IDs separated by commas", + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - use 1-%d unique six-digit hex IDs separated by commas", "Err - use 1-%d unique six-digit hex IDs, comma-separated"), FLOOD_PACKET_FILTER_BLACKLIST_REPLACE_MAX); } return; @@ -6478,12 +6479,12 @@ void MyMesh::setFloodPacketFilterBlacklist(const char* args, char* reply) { if (requested_slot >= 0) { if (requested_slot > flood_packet_filter_blacklist_count) { - snprintf(reply, 160, "Err - next blacklist slot is %u", + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - next blacklist slot is %u", "Err - next blacklist slot: %u"), (uint32_t)flood_packet_filter_blacklist_count + 1U); return; } if ((uint16_t)requested_slot + count > FLOOD_PACKET_FILTER_BLACKLIST_MAX) { - snprintf(reply, 160, "Err - blacklist range exceeds slot %u", + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - blacklist range exceeds slot %u", "Err - blacklist range past slot %u"), (uint32_t)FLOOD_PACKET_FILTER_BLACKLIST_MAX); return; } @@ -6513,7 +6514,7 @@ void MyMesh::setFloodPacketFilterBlacklist(const char* args, char* reply) { if (!saveFloodPacketFilterBlacklist()) { flood_packet_filter_blacklist_count = previous_count; memcpy(flood_packet_filter_blacklist, previous, sizeof(previous)); - strcpy(reply, "Err - unable to save flood filter blacklist"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - unable to save flood filter blacklist", "Err - flood filter blacklist not saved")); return; } if (requested_slot >= 0) { @@ -6538,7 +6539,7 @@ void MyMesh::deleteFloodPacketFilterBlacklist(const char* args, char* reply) { if (!delete_all && (!parseFloodFilterUnsigned(selector, FLOOD_PACKET_FILTER_BLACKLIST_MAX, slot) || slot == 0 || slot > flood_packet_filter_blacklist_count)) { - snprintf(reply, 160, "Err - use: del flood.filter.blacklist[.<1-%u>| all]", + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - use: del flood.filter.blacklist[.<1-%u>| all]", "Err - del flood.filter.blacklist[.<1-%u>| all]"), (uint32_t)FLOOD_PACKET_FILTER_BLACKLIST_MAX); return; } @@ -6564,7 +6565,7 @@ void MyMesh::deleteFloodPacketFilterBlacklist(const char* args, char* reply) { if (!saveFloodPacketFilterBlacklist()) { flood_packet_filter_blacklist_count = previous_count; memcpy(flood_packet_filter_blacklist, previous, sizeof(previous)); - strcpy(reply, "Err - unable to save flood filter blacklist"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - unable to save flood filter blacklist", "Err - flood filter blacklist not saved")); return; } if (delete_all) { @@ -8330,7 +8331,7 @@ void MyMesh::formatFloodPacketFilters(const char* args, char* reply, bool compac } uint8_t slot; if (!parseFloodFilterUnsigned(selector, flood_packet_filter_slots, slot) || slot == 0) { - snprintf(reply, 160, "Err - filter slot must be 1-%d", flood_packet_filter_slots); + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - filter slot must be 1-%d", "Err - filter slot: 1-%d"), flood_packet_filter_slots); return; } formatFloodPacketFilterDetail(slot - 1, reply, 160, compact); @@ -8402,14 +8403,14 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, size_t slot_len = (size_t)(cursor - slot_start); char slot_text[8]; if (slot_len == 0 || slot_len >= sizeof(slot_text)) { - snprintf(reply, 160, "Err - filter slot must be 1-%d", flood_packet_filter_slots); + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - filter slot must be 1-%d", "Err - filter slot: 1-%d"), flood_packet_filter_slots); return; } memcpy(slot_text, slot_start, slot_len); slot_text[slot_len] = 0; uint8_t slot; if (!parseFloodFilterUnsigned(slot_text, flood_packet_filter_slots, slot) || slot == 0) { - snprintf(reply, 160, "Err - filter slot must be 1-%d", flood_packet_filter_slots); + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - filter slot must be 1-%d", "Err - filter slot: 1-%d"), flood_packet_filter_slots); return; } requested_slot = slot - 1; @@ -8464,7 +8465,7 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, ? tokens[0] + strlen("type=") : floodFilterAsciiStartsWith(tokens[0], "t=") ? tokens[0] + 2 : tokens[0]; if (!parseFloodFilterPayloadType(type_text, payload_type)) { - strcpy(reply, "Err - packet type must be name, any, 0-15, or 0x00-0x0F"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - packet type must be name, any, 0-15, or 0x00-0x0F", "Err - type: name|any|0-15|0x00-0x0F")); return; } @@ -8673,7 +8674,7 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, if (floodFilterAsciiEqual(tokens[i], "tx=slow")) { scope_uses_slow_timing = true; } else if (!floodFilterAsciiEqual(tokens[i], "tx=fast")) { - strcpy(reply, "Err - tx timing must be fast or slow"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - tx timing must be fast or slow", "Err - tx: fast|slow")); return; } scope_timing_set = true; @@ -8685,7 +8686,7 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, } if (!normalizeFloodFilterScopeName(strchr(tokens[i], '=') + 1, scope_name, sizeof(scope_name))) { - strcpy(reply, "Err - scope must be a public name of at most 30 characters"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - scope must be a public name of at most 30 characters", "Err - scope: public name, max 30 chars")); return; } target_set = true; @@ -8953,7 +8954,7 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, if (!saveFloodPacketFilters()) { flood_packet_filters[slot] = previous; flood_channel_data_rule_slot = previous_channel_data_slot; - strcpy(reply, "Err - unable to save flood filter"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - unable to save flood filter", "Err - flood filter not saved")); return; } @@ -9003,7 +9004,7 @@ void MyMesh::formatFloodPacketFilters(const char* args, char* reply, bool compac if (*selector != 0) { uint8_t slot; if (!parseFloodFilterUnsigned(selector, flood_packet_filter_slots, slot) || slot == 0) { - snprintf(reply, 160, "Err - filter slot must be 1-%d", flood_packet_filter_slots); + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - filter slot must be 1-%d", "Err - filter slot: 1-%d"), flood_packet_filter_slots); return; } formatFloodPacketFilterDetail(slot - 1, reply, 160); @@ -9055,19 +9056,19 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, size_t slot_len = (size_t)(cursor - slot_start); char slot_text[8]; if (slot_len == 0 || slot_len >= sizeof(slot_text)) { - snprintf(reply, 160, "Err - filter slot must be 1-%d", flood_packet_filter_slots); + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - filter slot must be 1-%d", "Err - filter slot: 1-%d"), flood_packet_filter_slots); return; } memcpy(slot_text, slot_start, slot_len); slot_text[slot_len] = 0; uint8_t slot; if (!parseFloodFilterUnsigned(slot_text, flood_packet_filter_slots, slot) || slot == 0) { - snprintf(reply, 160, "Err - filter slot must be 1-%d", flood_packet_filter_slots); + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - filter slot must be 1-%d", "Err - filter slot: 1-%d"), flood_packet_filter_slots); return; } requested_slot = slot - 1; if (*cursor != ' ') { - strcpy(reply, "Err - packet type must be name, any, 0-15, or 0x00-0x0F"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - packet type must be name, any, 0-15, or 0x00-0x0F", "Err - type: name|any|0-15|0x00-0x0F")); return; } } @@ -9078,7 +9079,7 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, char params[140]; #endif if (strlen(cursor) >= sizeof(params)) { - strcpy(reply, "Err - filter parameters too long"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - filter parameters too long", "Err - filter params too long")); return; } @@ -9088,7 +9089,7 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, char* token = params; while (*token != 0) { if (token_count >= 7) { - strcpy(reply, "Err - too many flood filter parameters"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - too many flood filter parameters", "Err - excess flood filter parameters")); return; } tokens[token_count++] = token; @@ -9105,7 +9106,7 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, uint8_t payload_type; if (!parseFloodFilterPayloadType(tokens[0], payload_type)) { - strcpy(reply, "Err - packet type must be name, any, 0-15, or 0x00-0x0F"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - packet type must be name, any, 0-15, or 0x00-0x0F", "Err - type: name|any|0-15|0x00-0x0F")); return; } @@ -9146,7 +9147,7 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, if (floodFilterAsciiEqual(tokens[i], "tx=slow")) { scope_uses_slow_timing = true; } else if (!floodFilterAsciiEqual(tokens[i], "tx=fast")) { - strcpy(reply, "Err - tx timing must be fast or slow"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - tx timing must be fast or slow", "Err - tx: fast|slow")); return; } scope_timing_set = true; @@ -9157,7 +9158,7 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, } if (!normalizeFloodFilterScopeName(tokens[i] + strlen("scope="), scope_name, sizeof(scope_name))) { - strcpy(reply, "Err - scope must be a public name of at most 30 characters"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - scope must be a public name of at most 30 characters", "Err - scope: public name, max 30 chars")); return; } } else if (!hops_set && parseFloodFilterHopSpec(tokens[i], min_hops, max_hops)) { @@ -9168,11 +9169,11 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, } } if (scope_requires_region_match && scope_name[0] == 0) { - strcpy(reply, "Err - require=region requires scope="); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - require=region requires scope=", "Err - require=region needs scope=")); return; } if (scope_timing_set && scope_name[0] == 0) { - strcpy(reply, "Err - tx timing requires scope="); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - tx timing requires scope=", "Err - tx needs scope=")); return; } @@ -9217,7 +9218,7 @@ void MyMesh::setFloodPacketFilter(const char* args, char* reply, StrHelper::strzcpy(entry.scopeName(), scope_name, sizeof(entry.scopeName())); if (!saveFloodPacketFilters()) { entry = previous; - strcpy(reply, "Err - unable to save flood filter"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - unable to save flood filter", "Err - flood filter not saved")); return; } @@ -9232,20 +9233,20 @@ void MyMesh::deleteFloodPacketFilter(const char* args, char* reply) { if (*selector == '.') selector = skipFloodFilterSpaces(selector + 1); if (floodFilterAsciiEqual(selector, "all")) { if (!saveFloodPacketFilters(false, true)) { - strcpy(reply, "Err - unable to save flood filter"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - unable to save flood filter", "Err - flood filter not saved")); } else { if (flood_packet_filters) memset(flood_packet_filters, 0, sizeof(FloodPacketFilterEntry) * flood_packet_filter_slots); #if MESH_ENABLE_FLOOD_RULE_ENGINE flood_channel_data_rule_slot = 0xFF; #endif - strcpy(reply, "OK - all flood filters removed"); + strcpy(reply, MESH_CLI_REPLY_TEXT("OK - all flood filters removed", "OK - flood filters cleared")); } return; } uint8_t slot; if (!parseFloodFilterUnsigned(selector, flood_packet_filter_slots, slot) || slot == 0) { - snprintf(reply, 160, "Err - use: del flood.filter.<1-%d>|all", flood_packet_filter_slots); + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - use: del flood.filter.<1-%d>|all", "Err - del flood.filter.<1-%d>|all"), flood_packet_filter_slots); return; } int index = slot - 1; @@ -9266,7 +9267,7 @@ void MyMesh::deleteFloodPacketFilter(const char* args, char* reply) { #if MESH_ENABLE_FLOOD_RULE_ENGINE flood_channel_data_rule_slot = previous_channel_data_slot; #endif - strcpy(reply, "Err - unable to save flood filter"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - unable to save flood filter", "Err - flood filter not saved")); } else { strcpy(reply, "OK"); } @@ -9821,7 +9822,7 @@ static bool parseFloodChannelScopeIndex(const char*& cursor, int& index) { void MyMesh::formatFloodChannelScopeDetail(int index, char* reply, size_t reply_len) { if (index < 0 || index >= FLOOD_CHANNEL_SCOPE_SLOTS) { - snprintf(reply, reply_len, "Err - scope slot must be 1-%d", FLOOD_CHANNEL_SCOPE_SLOTS); + snprintf(reply, reply_len, MESH_CLI_REPLY_TEXT("Err - scope slot must be 1-%d", "Err - scope slot: 1-%d"), FLOOD_CHANNEL_SCOPE_SLOTS); return; } const auto& entry = floodChannelScopes()[index]; @@ -9879,7 +9880,7 @@ void MyMesh::formatFloodChannelScopes(const char* args, char* reply) { const char* cursor = args; int index = -1; if (!parseFloodChannelScopeIndex(cursor, index)) { - snprintf(reply, 160, "Err - scope slot must be 1-%d", FLOOD_CHANNEL_SCOPE_SLOTS); + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - scope slot must be 1-%d", "Err - scope slot: 1-%d"), FLOOD_CHANNEL_SCOPE_SLOTS); return; } if (*cursor != 0) { @@ -9956,7 +9957,7 @@ void MyMesh::setFloodChannelScope(const char* args, char* reply) { option + (option[0] == 'p' && option[1] == '=' ? 3 : 12), FLOOD_RETRY_BRIDGE_BUCKETS, bucket) || bucket == 0) { - strcpy(reply, "Err - path bucket must be 1-6"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - path bucket must be 1-6", "Err - path bucket: 1-6")); return; } path_set = true; @@ -9981,7 +9982,7 @@ void MyMesh::setFloodChannelScope(const char* args, char* reply) { target_text + strlen("scope="), direct_scope_name, sizeof(direct_scope_name))) { strcpy(reply, - "Err - scope must be a public name of at most 30 characters"); + MESH_CLI_REPLY_TEXT("Err - scope must be a public name of at most 30 characters", "Err - scope: public name, max 30 chars")); return; } } else { @@ -10151,7 +10152,7 @@ void MyMesh::deleteFloodChannelScope(const char* args, char* reply) { void MyMesh::formatFloodChannelScopeRequirementDetail( int index, char* reply, size_t reply_len) const { if (index < 0 || index >= FLOOD_CHANNEL_SCOPE_REQUIRE_SLOTS) { - snprintf(reply, reply_len, "Err - require slot must be 1-%d", + snprintf(reply, reply_len, MESH_CLI_REPLY_TEXT("Err - require slot must be 1-%d", "Err - require slot: 1-%d"), FLOOD_CHANNEL_SCOPE_REQUIRE_SLOTS); return; } @@ -10174,7 +10175,7 @@ void MyMesh::formatFloodChannelScopeRequirements(const char* args, const char* cursor = args; int index = -1; if (!parseFloodChannelScopeIndex(cursor, index)) { - snprintf(reply, 160, "Err - require slot must be 1-%d", + snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - require slot must be 1-%d", "Err - require slot: 1-%d"), FLOOD_CHANNEL_SCOPE_REQUIRE_SLOTS); return; } @@ -10219,7 +10220,7 @@ void MyMesh::setFloodChannelScopeRequirement(const char* args, char* reply) { char ignored_name[FLOOD_GROUP_MODERATION_NAME_LEN]; if (!parseFloodModerationChannel(channel_text, secret, key_len, hash_prefix, ignored_name, sizeof(ignored_name))) { - strcpy(reply, "Err - channel must be public, #name, or 128/256-bit key"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - channel must be public, #name, or 128/256-bit key", "Err - channel: public|#name|128/256-bit key")); return; } @@ -12474,7 +12475,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char * } if (region_load_active && sender && !sender->isAdmin() && !sender->isRegionMgr()) { - strcpy(reply, "Err - region load owned by admin/region manager"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - region load owned by admin/region manager", "Err - region load: admin/region manager only")); return; } @@ -13194,7 +13195,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char * && mesh::Utils::fromHex(pubkey, (int)(hex_len / 2), hex)) { uint32_t perms; if (!mesh::cli::parseUnsignedIntegerStrict(sp, perms) || perms > UINT8_MAX) { - strcpy(reply, "Err - permissions must be 0-255"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - permissions must be 0-255", "Err - permissions: 0-255")); } else if (acl.applyPermissions(self_id, pubkey, (int)(hex_len / 2), static_cast(perms))) { mesh::scheduleLazyPersistenceMutation( dirty_contacts_expiry, contacts_save_failures, @@ -13240,7 +13241,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char * } else if (strncmp(command, "send text.flood ", 16) == 0) { char* text = trimSpaces(command + 16); if (*text == 0) { - strcpy(reply, "Err - usage: send text.flood "); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - usage: send text.flood ", "Err - send text.flood ")); } else if (sendRepeatersFloodText(text)) { strcpy(reply, "OK"); } else { @@ -13275,7 +13276,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char * snprintf(reply, 160, "OK - saved off; rx.watchdog=%s", radio_timing.watchdogLabel(_prefs.rx_watchdog_enabled)); } else { - strcpy(reply, "Err - usage: set rx.watchdog "); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - usage: set rx.watchdog ", "Err - set rx.watchdog ")); } } else if (strncmp(command, "set battery.alert ", 18) == 0) { const char* value = command + 18; @@ -13286,22 +13287,22 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char * if (*region_name) { if (strchr(region_name, ' ') != NULL) { - strcpy(reply, "Err - region names cannot contain spaces"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - region names cannot contain spaces", "Err - region name: no spaces")); return; } region = region_map.findByName(region_name); if (region == NULL || region->isWildcard()) { - strcpy(reply, "Err - unknown or invalid alert region"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - unknown or invalid alert region", "Err - unknown/invalid alert region")); return; } } else { region = findNarrowestBatteryAlertRegion(ambiguous); if (region == NULL) { - strcpy(reply, "Err - define a usable region before enabling battery alerts"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - define a usable region before enabling battery alerts", "Err - battery alerts need a usable region first")); return; } if (ambiguous) { - strcpy(reply, "Err - multiple narrowest regions; specify one"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - multiple narrowest regions; specify one", "Err - specify one of the narrowest regions")); return; } } @@ -13324,14 +13325,14 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char * savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Err - usage: set battery.alert "); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - usage: set battery.alert ", "Err - set battery.alert ")); } } else if (strncmp(command, "set battery.alert.low ", 22) == 0) { uint8_t percent; if (!parseBatteryAlertPercent(command + 22, 1, 100, percent)) { - strcpy(reply, "Err - usage: set battery.alert.low <1-100>"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - usage: set battery.alert.low <1-100>", "Err - set battery.alert.low <1-100>")); } else if (percent <= _prefs.battery_alert_critical_percent) { - strcpy(reply, "Err - low must be greater than critical"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - low must be greater than critical", "Err - low must exceed critical")); } else { _prefs.battery_alert_low_percent = percent; next_battery_alert_check = 0; @@ -13341,9 +13342,9 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char * } else if (strncmp(command, "set battery.alert.critical ", 27) == 0) { uint8_t percent; if (!parseBatteryAlertPercent(command + 27, 0, 99, percent)) { - strcpy(reply, "Err - usage: set battery.alert.critical <0-99>"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - usage: set battery.alert.critical <0-99>", "Err - set battery.alert.critical <0-99>")); } else if (percent >= _prefs.battery_alert_low_percent) { - strcpy(reply, "Err - critical must be less than low"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Err - critical must be less than low", "Err - critical must be below low")); } else { _prefs.battery_alert_critical_percent = percent; next_battery_alert_check = 0; diff --git a/src/helpers/CLICommandUtils.h b/src/helpers/CLICommandUtils.h index d036424cf..eab2744f5 100644 --- a/src/helpers/CLICommandUtils.h +++ b/src/helpers/CLICommandUtils.h @@ -1,5 +1,6 @@ #pragma once +#include "CLIReplyText.h" #include #include #include diff --git a/src/helpers/CLIReplyText.h b/src/helpers/CLIReplyText.h new file mode 100644 index 000000000..315de80cf --- /dev/null +++ b/src/helpers/CLIReplyText.h @@ -0,0 +1,14 @@ +#pragma once + +// Constrained profiles may shorten human CLI prose. Command syntax, structured +// replies, protocol acknowledgements, destructive and rollback warnings keep +// their exact text. +#ifndef MESH_CLI_COMPACT_REPLIES +#define MESH_CLI_COMPACT_REPLIES 0 +#endif + +#if MESH_CLI_COMPACT_REPLIES +#define MESH_CLI_REPLY_TEXT(verbose, concise) concise +#else +#define MESH_CLI_REPLY_TEXT(verbose, concise) verbose +#endif diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index dce9ba037..8d227e6da 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -1,3 +1,4 @@ +#include "CLIReplyText.h" #include #if defined(USE_LR2021) #include @@ -262,7 +263,7 @@ bool CommonCLI::applyAndSavePrimaryRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, uint16_t preamble, char* reply) { const int8_t previous_power = _prefs->tx_power_dbm; if (!savePrimaryRadioParams(freq, bw, sf, cr, preamble)) { - mesh::cli::formatError(reply, 160, "radio settings could not be saved"); + mesh::cli::formatError(reply, 160, MESH_CLI_REPLY_TEXT("radio settings could not be saved", "radio settings not saved")); return false; } // Arm the role's reply handoff now, while the originating CLI command is @@ -3691,7 +3692,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re _callbacks->onManualClockSet(); uint32_t now = getRTCClock()->getCurrentTime(); DateTime dt = DateTime(now); - sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); + sprintf(reply, MESH_CLI_REPLY_TEXT("OK - clock set: %02d:%02d - %d/%d/%d UTC", "OK - clock: %02d:%02d - %d/%d/%d UTC"), dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); } else { strcpy(reply, "ERR: clock cannot go backwards"); } @@ -3866,7 +3867,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re _callbacks->onManualClockSet(); uint32_t now = getRTCClock()->getCurrentTime(); DateTime dt = DateTime(now); - sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); + sprintf(reply, MESH_CLI_REPLY_TEXT("OK - clock set: %02d:%02d - %d/%d/%d UTC", "OK - clock: %02d:%02d - %d/%d/%d UTC"), dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); } else { strcpy(reply, "(ERR: clock cannot go backwards)"); } @@ -3900,7 +3901,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re } else if (memcmp(command, "tempradio ", 10) == 0) { uint16_t preamble = 0; if (!mesh::RadioProfileCLI::parseSuffix(command + 10, 5, tmp, sizeof(tmp), preamble)) { - strcpy(reply, "Error, use: tempradio f,bw,sf,cr,minutes[,preamble]"); return; + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, use: tempradio f,bw,sf,cr,minutes[,preamble]", "Error, tempradio f,bw,sf,cr,minutes[,preamble]")); return; } const char *parts[5]; int num = mesh::Utils::parseTextParts(tmp, parts, 5); @@ -3965,7 +3966,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re _callbacks->clearRecentRepeaters(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, unsupported on this role"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, unsupported on this role", "Error, unsupported role")); } } else if (memcmp(command, "get ", 4) == 0) { handleGetCmd(sender_timestamp, command, reply); @@ -4047,7 +4048,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re } } else if (memcmp(command, "sensor set ", 11) == 0) { if (!copyCLIArgument(tmp, sizeof(tmp), &command[11])) { - strcpy(reply, "Error, argument too long"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, argument too long", "Error, arg too long")); return; } const char *parts[2]; @@ -4391,7 +4392,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 0,1, or 2"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 0,1, or 2", "Error, 0|1|2")); } return; } @@ -4781,18 +4782,18 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep } #endif if (isAdvancedRetryConfig(config) && !_callbacks->supportsAdvancedRetryConfig()) { - strcpy(reply, "Error, unsupported on this role"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, unsupported on this role", "Error, unsupported role")); return; } if (isBasicRetryConfig(config) && !_callbacks->supportsBasicRetryConfig()) { - strcpy(reply, "Error, retry configuration unsupported on this role"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, retry configuration unsupported on this role", "Error, retry unsupported for role")); return; } if (memcmp(config, "dutycycle ", 10) == 0) { float dc = 0.0f; if (!mesh::cli::parseDecimalStrict(&config[10], dc) || dc < 1.0f || dc > 100.0f) { - strcpy(reply, "ERROR: dutycycle must be 1-100"); + strcpy(reply, MESH_CLI_REPLY_TEXT("ERROR: dutycycle must be 1-100", "ERROR: dutycycle 1-100")); } else { _prefs->airtime_factor = (100.0f / dc) - 1.0f; savePrefs(); @@ -4816,7 +4817,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep mesh::cli::formatError(reply, 160, "use set system.watchdog on|off"); } #else - mesh::cli::formatError(reply, 160, "unsupported on this platform"); + mesh::cli::formatError(reply, 160, MESH_CLI_REPLY_TEXT("unsupported on this platform", "platform unsupported")); #endif } else if (memcmp(config, "af ", 3) == 0) { float factor = 0.0f; @@ -4825,7 +4826,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, invalid airtime factor"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, invalid airtime factor", "Error, bad airtime factor")); } } else if (memcmp(config, "int.thresh ", 11) == 0) { _prefs->interference_threshold = atoi(&config[11]); @@ -4847,7 +4848,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep } else { _prefs->agc_reset_interval = seconds / 4; savePrefs(); - sprintf(reply, "OK - interval rounded to %d", ((uint32_t) _prefs->agc_reset_interval) * 4); + sprintf(reply, MESH_CLI_REPLY_TEXT("OK - interval rounded to %d", "OK - rounded interval %d"), ((uint32_t) _prefs->agc_reset_interval) * 4); } } else if (memcmp(config, "multi.acks ", 11) == 0) { _prefs->multi_acks = atoi(&config[11]); @@ -4867,7 +4868,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "ERROR: telemetry.access must be all or acl"); + strcpy(reply, MESH_CLI_REPLY_TEXT("ERROR: telemetry.access must be all or acl", "ERROR: telemetry.access all|acl")); } } else if (memcmp(config, "flood.advert.interval ", 22) == 0) { uint32_t hours = 0; @@ -4889,7 +4890,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep uint32_t mins = 0; if (!parseUint32Strict(&config[16], mins) || (mins != 0 && (mins < MIN_LOCAL_ADVERT_INTERVAL || mins > 240))) { - sprintf(reply, "Error: interval must be 0 (off) or %d-240 minutes", MIN_LOCAL_ADVERT_INTERVAL); + sprintf(reply, MESH_CLI_REPLY_TEXT("Error: interval must be 0 (off) or %d-240 minutes", "Error: interval 0 (off) or %d-240 minutes"), MIN_LOCAL_ADVERT_INTERVAL); } else { const uint8_t previous = _prefs->advert_interval; _prefs->advert_interval = (uint8_t)(mins / 2); @@ -4940,9 +4941,9 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep _prefs->disable_fwd = strcmp(&config[7], "off") == 0; savePrefs(); _callbacks->onRetryConfigChanged(); - strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON"); + strcpy(reply, _prefs->disable_fwd ? MESH_CLI_REPLY_TEXT("OK - repeat is now OFF", "OK - repeat OFF") : MESH_CLI_REPLY_TEXT("OK - repeat is now ON", "OK - repeat ON")); } else { - strcpy(reply, "Error, must be on or off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be on or off", "Error, on|off")); } } else if (memcmp(config, "radio.rxgain ", 13) == 0) { const char* value = &config[13]; @@ -4984,7 +4985,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep if (changed) _callbacks->recalibrateNoiseFloor(); _prefs->radio_fem_rxgain = 1; savePrefs(); - strcpy(reply, "OK - LoRa FEM RX gain on"); + strcpy(reply, MESH_CLI_REPLY_TEXT("OK - LoRa FEM RX gain on", "OK - FEM RX gain on")); } else { mesh::cli::formatError(reply, 160, "failed to apply LoRa FEM RX gain"); } @@ -4994,7 +4995,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep if (changed) _callbacks->recalibrateNoiseFloor(); _prefs->radio_fem_rxgain = 0; savePrefs(); - strcpy(reply, "OK - LoRa FEM RX gain off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("OK - LoRa FEM RX gain off", "OK - FEM RX gain off")); } else { mesh::cli::formatError(reply, 160, "failed to apply LoRa FEM RX gain"); } @@ -5013,7 +5014,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep if (_board->setLoRaFemPaGainEnabled(true)) { _prefs->radio_fem_txgain = 1; savePrefs(); - strcpy(reply, "OK - LoRa FEM TX gain on"); + strcpy(reply, MESH_CLI_REPLY_TEXT("OK - LoRa FEM TX gain on", "OK - FEM TX gain on")); } else { mesh::cli::formatError(reply, 160, "failed to apply LoRa FEM TX gain"); } @@ -5021,7 +5022,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep if (_board->setLoRaFemPaGainEnabled(false)) { _prefs->radio_fem_txgain = 0; savePrefs(); - strcpy(reply, "OK - LoRa FEM TX gain off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("OK - LoRa FEM TX gain off", "OK - FEM TX gain off")); } else { mesh::cli::formatError(reply, 160, "failed to apply LoRa FEM TX gain"); } @@ -5144,14 +5145,14 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep if (manual_timings_requested && (rx_us < RX_POWERSAVING_MIN_MANUAL_PERIOD_US || sleep_us < RX_POWERSAVING_MIN_MANUAL_PERIOD_US)) { - sprintf(reply, "ERROR: range is %lu-%lu us", + sprintf(reply, MESH_CLI_REPLY_TEXT("ERROR: range is %lu-%lu us", "ERROR: range %lu-%lu us"), (unsigned long)RX_POWERSAVING_MIN_MANUAL_PERIOD_US, (unsigned long)RX_POWERSAVING_MAX_PERIOD_US); return; } if (!isValidRxPowerSavingPeriod(rx_us) || !isValidRxPowerSavingPeriod(sleep_us)) { - sprintf(reply, "ERROR: range is %lu-%lu us", + sprintf(reply, MESH_CLI_REPLY_TEXT("ERROR: range is %lu-%lu us", "ERROR: range %lu-%lu us"), (unsigned long)RX_POWERSAVING_MIN_MANUAL_PERIOD_US, (unsigned long)RX_POWERSAVING_MAX_PERIOD_US); return; @@ -5223,7 +5224,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep appendRxPowerSavingAdjustmentNote(reply, _prefs, sf, bw); _radio_profiles.appendPrimaryChirpWarning(reply, 160, sf, bw, preamble); } else { - strcpy(reply, "Error, invalid radio params"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, invalid radio params", "Error, bad radio params")); } } else if (memcmp(config, "radioat ", 8) == 0) { float freq, bw; @@ -5233,9 +5234,9 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep char legacy[120]; if (!mesh::RadioProfileCLI::parseSuffix(&config[8], 5, legacy, sizeof(legacy), preamble) || !parseScheduledRadioArgs(legacy, false, freq, bw, sf, cr, start_time, end_time, _rtc->getCurrentTime())) { - strcpy(reply, "Error, use: set radioat f,bw,sf,cr,start (UTC or +minutes)"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, use: set radioat f,bw,sf,cr,start (UTC or +minutes)", "Error, set radioat f,bw,sf,cr,start (UTC or +minutes)")); } else if (!acceptsPrimaryRadioParams(freq, bw, sf, cr, preamble)) { - strcpy(reply, "Error, invalid radio params"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, invalid radio params", "Error, bad radio params")); } else { _callbacks->addScheduledRadioParams(false, freq, bw, sf, cr, start_time, end_time, reply, preamble); if (strncmp(reply, "OK", 2) == 0) { @@ -5251,9 +5252,9 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep char legacy[120]; if (!mesh::RadioProfileCLI::parseSuffix(&config[12], 6, legacy, sizeof(legacy), preamble) || !parseScheduledRadioArgs(legacy, true, freq, bw, sf, cr, start_time, end_time, _rtc->getCurrentTime())) { - strcpy(reply, "Error, use: set tempradioat f,bw,sf,cr,start,end (UTC or +minutes)"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, use: set tempradioat f,bw,sf,cr,start,end (UTC or +minutes)", "Error, set tempradioat f,bw,sf,cr,start,end (UTC or +minutes)")); } else if (!acceptsPrimaryRadioParams(freq, bw, sf, cr, preamble)) { - strcpy(reply, "Error, invalid radio params"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, invalid radio params", "Error, bad radio params")); } else { _callbacks->addScheduledRadioParams(true, freq, bw, sf, cr, start_time, end_time, reply, preamble); if (strncmp(reply, "OK", 2) == 0) { @@ -5268,7 +5269,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, invalid latitude"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, invalid latitude", "Error, bad latitude")); } } else if (memcmp(config, "lon ", 4) == 0) { float longitude = 0.0f; @@ -5277,7 +5278,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, invalid longitude"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, invalid longitude", "Error, bad longitude")); } } else if (memcmp(config, "rxdelay ", 8) == 0) { float db = 0.0f; @@ -5287,7 +5288,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 0-20"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 0-20", "Error, 0-20")); } } else if (memcmp(config, "txdelay ", 8) == 0) { float f = 0.0f; @@ -5297,7 +5298,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 0-2"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 0-2", "Error, 0-2")); } } else if (memcmp(config, "flood.max.unscoped ", 19) == 0) { uint32_t m; @@ -5334,7 +5335,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 0-2"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 0-2", "Error, 0-2")); } } else if (memcmp(config, "retry.preset ", 13) == 0) { uint8_t preset; @@ -5343,7 +5344,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); sprintf(reply, "OK - %s", retryPresetName(_prefs->retry_preset)); } else { - strcpy(reply, "Error, must be infra, rooftop, or mobile"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be infra, rooftop, or mobile", "Error, infra|rooftop|mobile")); } } else if (memcmp(config, "direct.retry ", 13) == 0) { if (strcmp(&config[13], "on") == 0) { @@ -5357,7 +5358,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep _callbacks->onRetryConfigChanged(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be on or off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be on or off", "Error, on|off")); } } else if (memcmp(config, "direct.retry.heard ", 19) == 0) { if (strcmp(&config[19], "on") == 0) { @@ -5369,11 +5370,11 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be on or off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be on or off", "Error, on|off")); } } else if (memcmp(config, "direct.retry.margin ", 20) == 0) { if (!looksNumeric(&config[20])) { - strcpy(reply, "Error, must be 0-40 dB"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 0-40 dB", "Error, 0-40 dB")); } else { int16_t margin_x4 = parseSnrDbX4(&config[20]); if (margin_x4 >= 0 && margin_x4 <= 160) { @@ -5382,7 +5383,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 0-40 dB"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 0-40 dB", "Error, 0-40 dB")); } } } else if (memcmp(config, "direct.retry.count ", 19) == 0) { @@ -5393,7 +5394,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 1-15"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 1-15", "Error, 1-15")); } } else if (memcmp(config, "direct.retry.base ", 18) == 0) { int base_ms = looksUnsignedInteger(&config[18]) ? _atoi(&config[18]) : -1; @@ -5403,7 +5404,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 10-5000 ms"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 10-5000 ms", "Error, 10-5000 ms")); } } else if (memcmp(config, "direct.retry.step ", 18) == 0) { int step_ms = looksUnsignedInteger(&config[18]) ? _atoi(&config[18]) : -1; @@ -5413,7 +5414,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 0-5000 ms"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 0-5000 ms", "Error, 0-5000 ms")); } } else if (memcmp(config, "flood.channel.data.hops ", 24) == 0) { uint8_t max_hops; @@ -5422,7 +5423,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be all or 1-7"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be all or 1-7", "Error, all|1-7")); } } else if (memcmp(config, "flood.channel.data ", 19) == 0) { if (strcmp(&config[19], "on") == 0) { @@ -5434,7 +5435,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be on or off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be on or off", "Error, on|off")); } } else if (memcmp(config, "flood.retry.count ", 18) == 0) { int attempts = looksUnsignedInteger(&config[18]) ? _atoi(&config[18]) : -1; @@ -5445,7 +5446,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep _callbacks->onRetryConfigChanged(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 0-15"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 0-15", "Error, 0-15")); } } else if (memcmp(config, "flood.retry.path ", 17) == 0) { uint8_t path_gate; @@ -5458,7 +5459,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 0-63 or off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 0-63 or off", "Error, 0-63|off")); } } else if (memcmp(config, "flood.retry.group.path ", 23) == 0) { uint8_t path_gate; @@ -5470,7 +5471,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be 0-63 or off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be 0-63 or off", "Error, 0-63|off")); } } else if (memcmp(config, "flood.retry.prefixes ", 21) == 0) { if (parseFloodRetryPrefixList(_prefs->flood_retry_prefixes, FLOOD_RETRY_PREFIX_SLOTS, &config[21])) { @@ -5479,7 +5480,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep strcpy(reply, "OK"); } } else { - sprintf(reply, "Error, use up to %u comma-separated 3-byte hex prefixes", + sprintf(reply, MESH_CLI_REPLY_TEXT("Error, use up to %u comma-separated 3-byte hex prefixes", "Error, max %u comma-separated 3-byte hex prefixes"), (unsigned int)FLOOD_RETRY_PREFIX_SLOTS); } } else if (memcmp(config, "flood.retry.ignore ", 19) == 0) { @@ -5488,7 +5489,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - sprintf(reply, "Error, use up to %u comma-separated 3-byte hex prefixes", + sprintf(reply, MESH_CLI_REPLY_TEXT("Error, use up to %u comma-separated 3-byte hex prefixes", "Error, max %u comma-separated 3-byte hex prefixes"), (unsigned int)FLOOD_RETRY_IGNORE_PREFIXES); } } else if (memcmp(config, "flood.retry.advert ", 19) == 0) { @@ -5501,7 +5502,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be on or off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be on or off", "Error, on|off")); } } else if (memcmp(config, "flood.retry.bridge ", 19) == 0) { if (strcmp(&config[19], "on") == 0) { @@ -5519,7 +5520,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, must be on or off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be on or off", "Error, on|off")); } } else if (memcmp(config, "flood.retry.bucket ", 19) == 0) { const char* params = &config[19]; @@ -5536,12 +5537,12 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep strcpy(reply, "OK"); } } else { - sprintf(reply, "Error, use up to %u comma-separated 3-byte hex prefixes", + sprintf(reply, MESH_CLI_REPLY_TEXT("Error, use up to %u comma-separated 3-byte hex prefixes", "Error, max %u comma-separated 3-byte hex prefixes"), (unsigned int)FLOOD_RETRY_BUCKET_PREFIXES); } } else if (memcmp(config, "direct.retry.cr ", 16) == 0) { if (!copyCLIArgument(tmp, sizeof(tmp), &config[16])) { - strcpy(reply, "Error, argument too long"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, argument too long", "Error, arg too long")); return; } if (strcmp(&config[16], "off") == 0) { @@ -5554,7 +5555,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, use on or off on this role"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, use on or off on this role", "Error, on|off")); } } else { const char *parts[4]; @@ -5573,15 +5574,15 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error, SNR must fit -32.00..31.75 dB"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, SNR must fit -32.00..31.75 dB", "Error, SNR -32.00..31.75 dB")); } } else { - strcpy(reply, "Error, use CR4,CR5,CR7,CR8 SNRs or off"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, use CR4,CR5,CR7,CR8 SNRs or off", "Error, CR4,CR5,CR7,CR8 SNRs|off")); } } } else if (memcmp(config, "recent.repeater ", 16) == 0) { if (!copyCLIArgument(tmp, sizeof(tmp), &config[16])) { - strcpy(reply, "Error, argument too long"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, argument too long", "Error, arg too long")); return; } const char *parts[2]; @@ -5590,15 +5591,15 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep uint8_t prefix_len = 0; int16_t snr_x4 = 12; // default to +3.0 dB when omitted or invalid if (num < 1 || !parseHashPrefix(parts[0], prefix, prefix_len)) { - strcpy(reply, "Error, use: set recent.repeater [snr_db]"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, use: set recent.repeater [snr_db]", "Error, set recent.repeater [snr_db]")); } else if (num > 1 && !looksNumeric(parts[1])) { - strcpy(reply, "Error, SNR must be numeric"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, SNR must be numeric", "Error, numeric SNR needed")); } else { if (num > 1) { snr_x4 = parseSnrDbX4(parts[1]); } if (snr_x4 < -128 || snr_x4 > 127) { - strcpy(reply, "Error, SNR must fit -32.00..31.75 dB"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, SNR must fit -32.00..31.75 dB", "Error, SNR -32.00..31.75 dB")); } else if (_callbacks->setRecentRepeater(prefix, prefix_len, (int8_t)snr_x4)) { char prefix_hex[RECENT_REPEATER_PREFIX_MAX_BYTES * 2 + 1]; char snr[12]; @@ -5607,7 +5608,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep formatSnrDbX4Short(snr, sizeof(snr), snr_x4); sprintf(reply, "OK - set %s at %s SNR", prefix_hex, snr); } else { - strcpy(reply, "Error, table rejected prefix"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, table rejected prefix", "Error, prefix rejected")); } } } else if (memcmp(config, "owner.info ", 11) == 0) { @@ -5633,7 +5634,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep mode = LOOP_DETECT_STRICT; } else { mode = 0xFF; - strcpy(reply, "Error, must be: off, minimal, moderate, or strict"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, must be: off, minimal, moderate, or strict", "Error, off|minimal|moderate|strict")); } if (mode != 0xFF) { _prefs->loop_detect = mode; @@ -5647,7 +5648,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep if (!mesh::cli::parseIntegerStrict(&config[3], power)) { mesh::cli::formatError(reply, 160, "invalid TX power"); } else if (!mesh::isLoRaTxPowerValid(power, _prefs->freq)) { - sprintf(reply, "Error: TX power range is %d to %d dBm", + sprintf(reply, MESH_CLI_REPLY_TEXT("Error: TX power range is %d to %d dBm", "Error: TX power %d..%d dBm"), (int)minimum, (int)maximum); } else { applyAndSaveTxPower(static_cast(power), reply); @@ -5659,7 +5660,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep applyAndSavePrimaryRadioParams(freq, _prefs->bw, _prefs->sf, _prefs->cr, _prefs->primary_radio_preamble, reply); } else { - strcpy(reply, "Error, invalid frequency"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, invalid frequency", "Error, bad frequency")); } #ifdef WITH_BRIDGE } else if (memcmp(config, "bridge.enabled ", 15) == 0) { @@ -6167,11 +6168,11 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep } #endif if (isAdvancedRetryConfig(config) && !_callbacks->supportsAdvancedRetryConfig()) { - strcpy(reply, "Error, unsupported on this role"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, unsupported on this role", "Error, unsupported role")); return; } if (isBasicRetryConfig(config) && !_callbacks->supportsBasicRetryConfig()) { - strcpy(reply, "Error, retry configuration unsupported on this role"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, retry configuration unsupported on this role", "Error, retry unsupported for role")); return; } mesh::cli::RecentRepeaterGetQuery recent_query; @@ -6186,7 +6187,7 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep #if defined(NRF52_PLATFORM) sprintf(reply, "> %s", _prefs->system_watchdog_enabled ? "on" : "off"); #else - mesh::cli::formatError(reply, 160, "unsupported on this platform"); + mesh::cli::formatError(reply, 160, MESH_CLI_REPLY_TEXT("unsupported on this platform", "platform unsupported")); #endif } else if (configKeyEquals(config, "af")) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor)); @@ -6357,7 +6358,7 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep recent_query.search_prefix_len == 0 ? NULL : recent_query.search_prefix, recent_query.search_prefix_len); } else if (recent_query_match == mesh::cli::RecentRepeaterGetMatch::Invalid) { - strcpy(reply, "Error, use: get recent.repeaters [page] | search <2|4|6 hex> [page]"); + strcpy(reply, MESH_CLI_REPLY_TEXT("Error, use: get recent.repeaters [page] | search <2|4|6 hex> [page]", "Error, get recent.repeaters [page] | search <2|4|6 hex> [page]")); } else if (configKeyEquals(config, "owner.info")) { *reply++ = '>'; *reply++ = ' '; @@ -6513,7 +6514,7 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep #ifdef NRF52_POWER_MANAGEMENT strcpy(reply, _board->isExternalPowered() ? "> external" : "> battery"); #else - strcpy(reply, "ERROR: Power management not supported"); + strcpy(reply, MESH_CLI_REPLY_TEXT("ERROR: Power management not supported", "ERROR: Power management unsupported")); #endif } else if (configKeyEquals(config, "pwrmgt.bootreason")) { sprintf(reply, "> Reset: %s; Shutdown: %s", @@ -6523,7 +6524,7 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep #ifdef NRF52_POWER_MANAGEMENT sprintf(reply, "> %u mV", _board->getBootVoltage()); #else - strcpy(reply, "ERROR: Power management not supported"); + strcpy(reply, MESH_CLI_REPLY_TEXT("ERROR: Power management not supported", "ERROR: Power management unsupported")); #endif } else if (configKeyEquals(config, "reboot.interval")) { if (_prefs->reboot_interval == 0) { @@ -6608,7 +6609,7 @@ static bool processRegionDefSegment(RegionMap* map, char* tok, RegionEntry** cur if (jump) { RegionEntry* j = map->findByNamePrefix(jump); - if (j == NULL) { snprintf(reply, 160, "Err - unknown or ambiguous jump: %s", jump); return false; } + if (j == NULL) { snprintf(reply, 160, MESH_CLI_REPLY_TEXT("Err - unknown or ambiguous jump: %s", "Err - unknown/ambiguous jump: %s"), jump); return false; } *cursor = j; } else { *cursor = r; diff --git a/src/helpers/FloodAdvertCLI.h b/src/helpers/FloodAdvertCLI.h index 5bfaf4f41..3bdb1cb96 100644 --- a/src/helpers/FloodAdvertCLI.h +++ b/src/helpers/FloodAdvertCLI.h @@ -1,5 +1,7 @@ #pragma once +#include "CLIReplyText.h" + #include "FloodAdvertLimiter.h" #include "FloodCommandAliases.h" #include @@ -43,7 +45,7 @@ inline bool handleFloodAdvertGet(FloodAdvertLimiter* limiter, const char* comman return true; } if (!limiter) { - strcpy(reply, "ERR: advert limiter unavailable on this role"); + strcpy(reply, MESH_CLI_REPLY_TEXT("ERR: advert limiter unavailable on this role", "ERR: advert limiter unavailable for role")); return true; } @@ -54,7 +56,7 @@ inline bool handleFloodAdvertGet(FloodAdvertLimiter* limiter, const char* comman if (total == 0 && !detail && number == 1) { strcpy(reply, "> no rate-limited adverts"); } else if (offset >= total) { - strcpy(reply, "ERR: advert list index out of range"); + strcpy(reply, MESH_CLI_REPLY_TEXT("ERR: advert list index out of range", "ERR: advert index out of range")); } else if (detail) { const auto& row = rows[0]; char key[PUB_KEY_SIZE * 2 + 1]; @@ -100,11 +102,11 @@ inline bool handleFloodAdvertClear(FloodAdvertLimiter* limiter, const char* comm if (*end || !valid) { strcpy(reply, "ERR: clear flood.advert all|<64-hex-key>"); } else if (!limiter) { - strcpy(reply, "ERR: advert limiter unavailable on this role"); + strcpy(reply, MESH_CLI_REPLY_TEXT("ERR: advert limiter unavailable on this role", "ERR: advert limiter unavailable for role")); } else { if (all) limiter->reset(); else limiter->clear(key); - strcpy(reply, all ? "OK - all advert limit history cleared" : "OK - key advert limit history cleared"); + strcpy(reply, all ? MESH_CLI_REPLY_TEXT("OK - all advert limit history cleared", "OK - all advert history cleared") : MESH_CLI_REPLY_TEXT("OK - key advert limit history cleared", "OK - key advert history cleared")); } return true; } diff --git a/src/helpers/LedSettings.cpp b/src/helpers/LedSettings.cpp index 01abe5c54..94ebc35f7 100644 --- a/src/helpers/LedSettings.cpp +++ b/src/helpers/LedSettings.cpp @@ -1,3 +1,4 @@ +#include "CLIReplyText.h" #include "LedSettings.h" #include "CheckedSettingsFile.h" #include @@ -80,12 +81,12 @@ bool handleCommand(const char* command, char* reply, size_t capacity) { if (*value && *value != ' ' && *value != '\t') return false; const char* message; if (get) { - message = *value ? "Error: usage get leds" : enabled() ? "> on" : "> off"; + message = *value ? MESH_CLI_REPLY_TEXT("Error: usage get leds", "Error: get leds") : enabled() ? "> on" : "> off"; } else { while (*value == ' ' || *value == '\t') ++value; const bool on = strcmp(value, "on") == 0; if (!on && strcmp(value, "off") != 0) { - message = "Error: usage set leds on|off"; + message = MESH_CLI_REPLY_TEXT("Error: usage set leds on|off", "Error: set leds on|off"); } else if (!main_board || !saveSetting(on)) { message = "Error: LED setting save unconfirmed; reboot may differ"; } else { diff --git a/src/helpers/PowerSavingDiagnostics.h b/src/helpers/PowerSavingDiagnostics.h index 3f72a9dd8..d407ca29c 100644 --- a/src/helpers/PowerSavingDiagnostics.h +++ b/src/helpers/PowerSavingDiagnostics.h @@ -1,5 +1,7 @@ #pragma once +#include "CLIReplyText.h" + #include #include #include @@ -315,17 +317,17 @@ inline bool handleCommand(const char* command, char* reply, size_t capacity, if (argument(tail, "on") || argument(tail, "off")) { const bool on = argument(tail, "on"); setEnabled(on, now_ms); - snprintf(reply, capacity, "OK - power diagnostics %s (RAM only; sleep policy unchanged)", + snprintf(reply, capacity, MESH_CLI_REPLY_TEXT("OK - power diagnostics %s (RAM only; sleep policy unchanged)", "OK - power diag %s (RAM only; sleep policy unchanged)"), on ? "on" : "off"); - } else snprintf(reply, capacity, "Error: use set powersaving.diag on|off"); + } else snprintf(reply, capacity, MESH_CLI_REPLY_TEXT("Error: use set powersaving.diag on|off", "Error: set powersaving.diag on|off")); return true; } tail = suffix(command, "clear powersaving.diag"); if (tail) { if (!*tail) { reset(now_ms); - snprintf(reply, capacity, "OK - power diagnostics cleared; sleep policy unchanged"); - } else snprintf(reply, capacity, "Error: use clear powersaving.diag"); + snprintf(reply, capacity, MESH_CLI_REPLY_TEXT("OK - power diagnostics cleared; sleep policy unchanged", "OK - power diag cleared; sleep policy unchanged")); + } else snprintf(reply, capacity, MESH_CLI_REPLY_TEXT("Error: use clear powersaving.diag", "Error: clear powersaving.diag")); return true; } tail = suffix(command, "get powersaving.diag"); diff --git a/src/helpers/RadioProfileCLI.cpp b/src/helpers/RadioProfileCLI.cpp index 04c171d30..d6dbbd5c1 100644 --- a/src/helpers/RadioProfileCLI.cpp +++ b/src/helpers/RadioProfileCLI.cpp @@ -1,3 +1,4 @@ +#include "CLIReplyText.h" #include "RadioProfileCLI.h" #include "CarrierTools.h" #if MESH_ENABLE_CARRIER_TOOLS @@ -1144,7 +1145,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity, reply_setting_ = 0xb0 | policy | (force ? 8 : 0); if (!save(saved_, primary_preamble_, cross_)) { reply_setting_ = previous; - cli::formatError(reply, capacity, "settings could not be saved"); + cli::formatError(reply, capacity, MESH_CLI_REPLY_TEXT("settings could not be saved", "settings not saved")); } else { publish(); snprintf(reply, capacity, "OK - tx.reply=%s%s", radioTxPolicyName(policy), force ? " force" : ""); @@ -1182,7 +1183,7 @@ 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 cli::formatError(reply, capacity, "settings could not be saved"); + else cli::formatError(reply, capacity, MESH_CLI_REPLY_TEXT("settings could not be saved", "settings not saved")); } else cli::formatError(reply, capacity, "use get/set radio2.cross [auto|on|off]"); return true; } @@ -1249,7 +1250,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity, return true; } if (!save({}, primary_preamble_, cross_, 0)) { - cli::formatError(reply, capacity, "settings could not be saved"); return true; + cli::formatError(reply, capacity, MESH_CLI_REPLY_TEXT("settings could not be saved", "settings not saved")); return true; } noteSecondaryManualChange(); saved_ = {}; manual_secondary_ = false; @@ -1264,7 +1265,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))) { - cli::formatError(reply, capacity, "settings could not be saved"); return true; + cli::formatError(reply, capacity, MESH_CLI_REPLY_TEXT("settings could not be saved", "settings not saved")); return true; } if (remote_command_) { stageRemoteMutation(base ? RemoteMutation::Off : RemoteMutation::TempOff); @@ -1287,7 +1288,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)) { - cli::formatError(reply, capacity, "slot must be 1-4 or all"); return true; + cli::formatError(reply, capacity, MESH_CLI_REPLY_TEXT("slot must be 1-4 or all", "slot: 1-4|all")); return true; } const unsigned first = scheduled_temp ? 4 : 0; if (verb == Delete) { @@ -1326,7 +1327,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity, } return true; } - if (verb != Set) { cli::formatError(reply, capacity, "invalid radio2 command"); return true; } + if (verb != Set) { cli::formatError(reply, capacity, MESH_CLI_REPLY_TEXT("invalid radio2 command", "bad radio2 command")); return true; } if (base) { const char* preset = cli::radio2PresetTuple(args); if (preset) args = preset; @@ -1354,15 +1355,15 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity, if (valid) valid = radio_->validateProfile(config.params); RadioProfiles preview = *radio_->profiles(); preview.setSecondary(config, false); if (valid) valid = preview.automaticPreambleFits(); - if (!valid) { snprintf(reply, capacity, "Error: use %s f,bw,sf,cr,rx|rxtx%s[,preamble|auto]", key, + if (!valid) { snprintf(reply, capacity, MESH_CLI_REPLY_TEXT("Error: use %s f,bw,sf,cr,rx|rxtx%s[,preamble|auto]", "Error: %s f,bw,sf,cr,rx|rxtx%s[,preamble|auto]"), key, scheduled_temp ? ",start,end (UTC or +minutes)" : scheduled ? ",start (UTC or +minutes)" : temporary ? ",minutes" : ""); return true; } if (base && !mediaMeshScopeReady(config)) { - cli::formatError(reply, capacity, "MediaMesh region unavailable or full"); return true; + cli::formatError(reply, capacity, MESH_CLI_REPLY_TEXT("MediaMesh region unavailable or full", "MediaMesh region unavailable/full")); return true; } if (scheduled) { if (!epoch || start <= epoch || (uint64_t)(start-epoch) * 1000 > 0x7fffffffULL || (scheduled_temp && (end <= start || (uint64_t)(end-epoch) * 1000 > 0x7fffffffULL))) { - cli::formatError(reply, capacity, "future UTC start/end required, within 24 days"); return true; + cli::formatError(reply, capacity, MESH_CLI_REPLY_TEXT("future UTC start/end required, within 24 days", "future UTC start/end within 24 days")); return true; } if (scheduled_temp) { const uint64_t pending_left = temp_pending_ @@ -1397,7 +1398,7 @@ 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; } - cli::formatError(reply, capacity, "all four schedule slots occupied"); return true; + cli::formatError(reply, capacity, MESH_CLI_REPLY_TEXT("all four schedule slots occupied", "all four slots full")); return true; } if (temporary) { for (const auto& s : schedules_) if (s.active && s.temporary) { @@ -1429,7 +1430,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 cli::formatError(reply, capacity, "settings could not be saved"); + } else cli::formatError(reply, capacity, MESH_CLI_REPLY_TEXT("settings could not be saved", "settings not saved")); if (!strncmp(reply, "OK", 2)) appendSecondaryWarnings(reply, capacity, preview); return true; } diff --git a/src/helpers/StorageLayout.cpp b/src/helpers/StorageLayout.cpp index c6fff525f..16a9fb783 100644 --- a/src/helpers/StorageLayout.cpp +++ b/src/helpers/StorageLayout.cpp @@ -1,3 +1,4 @@ +#include "CLIReplyText.h" #include "StorageLayout.h" #if defined(ESP32_PLATFORM) @@ -185,7 +186,7 @@ bool handleStorageLayoutGet(const char* config, MainBoard& board, char* reply, if (match == StorageLayoutGetMatch::NotMatched) return false; if (reply == nullptr || reply_size == 0) return true; if (match == StorageLayoutGetMatch::InvalidArguments) { - snprintf(reply, reply_size, "Error: use get storage.layout"); + snprintf(reply, reply_size, MESH_CLI_REPLY_TEXT("Error: use get storage.layout", "Error: get storage.layout")); } else { formatStorageLayout(board, reply, reply_size); } diff --git a/src/helpers/WirelessControl.h b/src/helpers/WirelessControl.h index 7da2abca5..8a04a638f 100644 --- a/src/helpers/WirelessControl.h +++ b/src/helpers/WirelessControl.h @@ -1,5 +1,7 @@ #pragma once +#include "CLIReplyText.h" + #include #include #include @@ -141,7 +143,7 @@ public: const uint8_t available = backend_ ? backend_->available() : 0; const char* name = command.scope == All ? "2.4ghz" : command.scope == EspNow ? "espnow" : "wifi"; if (command.action == Action::Invalid) { - snprintf(reply, size, "Error: use set wifi/espnow on|off [force]; set 2.4ghz on [all|force]|off [force]"); + snprintf(reply, size, MESH_CLI_REPLY_TEXT("Error: use set wifi/espnow on|off [force]; set 2.4ghz on [all|force]|off [force]", "Error: set wifi/espnow on|off [force]; set 2.4ghz on [all|force]|off [force]")); return true; } const uint8_t current = enabled(); @@ -158,7 +160,7 @@ public: } #endif if (!(available & command.scope)) { - snprintf(reply, size, "Error: requested wireless service unavailable in this build/boot"); + snprintf(reply, size, MESH_CLI_REPLY_TEXT("Error: requested wireless service unavailable in this build/boot", "Error: wireless service unavailable this build/boot")); return true; } const bool on = command.action == Action::On || command.action == Action::OnAll; @@ -167,7 +169,7 @@ public: return true; } if (command.scope != All && on && master_off_) { - snprintf(reply, size, "Error: 2.4ghz is off; use set 2.4ghz on first"); + snprintf(reply, size, MESH_CLI_REPLY_TEXT("Error: 2.4ghz is off; use set 2.4ghz on first", "Error: 2.4ghz off; set 2.4ghz on first")); return true; } const uint8_t scope = command.scope & available; diff --git a/test/fixtures/cli_reply_text.json b/test/fixtures/cli_reply_text.json new file mode 100644 index 000000000..933d18401 --- /dev/null +++ b/test/fixtures/cli_reply_text.json @@ -0,0 +1,598 @@ +{ + "schema": 1, + "profiles": [ + "wio-e5-mini_repeater" + ], + "messages": [ + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, unsupported on this role", + "concise": "Error, unsupported role" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, retry configuration unsupported on this role", + "concise": "Error, retry unsupported for role" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "ERROR: dutycycle must be 1-100", + "concise": "ERROR: dutycycle 1-100" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, invalid airtime factor", + "concise": "Error, bad airtime factor" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "ERROR: telemetry.access must be all or acl", + "concise": "ERROR: telemetry.access all|acl" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error: interval must be 0 (off) or %d-240 minutes", + "concise": "Error: interval 0 (off) or %d-240 minutes" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be 0,1, or 2", + "concise": "Error, 0|1|2" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be on or off", + "concise": "Error, on|off" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be 0-20", + "concise": "Error, 0-20" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be 0-2", + "concise": "Error, 0-2" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be 0-40 dB", + "concise": "Error, 0-40 dB" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be 1-15", + "concise": "Error, 1-15" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be 10-5000 ms", + "concise": "Error, 10-5000 ms" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be 0-5000 ms", + "concise": "Error, 0-5000 ms" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be all or 1-7", + "concise": "Error, all|1-7" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be 0-15", + "concise": "Error, 0-15" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be 0-63 or off", + "concise": "Error, 0-63|off" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be infra, rooftop, or mobile", + "concise": "Error, infra|rooftop|mobile" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, use up to %u comma-separated 3-byte hex prefixes", + "concise": "Error, max %u comma-separated 3-byte hex prefixes" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, use on or off on this role", + "concise": "Error, on|off" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, SNR must fit -32.00..31.75 dB", + "concise": "Error, SNR -32.00..31.75 dB" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, use CR4,CR5,CR7,CR8 SNRs or off", + "concise": "Error, CR4,CR5,CR7,CR8 SNRs|off" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, must be: off, minimal, moderate, or strict", + "concise": "Error, off|minimal|moderate|strict" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error: TX power range is %d to %d dBm", + "concise": "Error: TX power %d..%d dBm" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, invalid radio params", + "concise": "Error, bad radio params" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, invalid latitude", + "concise": "Error, bad latitude" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, invalid longitude", + "concise": "Error, bad longitude" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, table rejected prefix", + "concise": "Error, prefix rejected" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Err - unknown or ambiguous jump: %s", + "concise": "Err - unknown/ambiguous jump: %s" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "ERROR: Power management not supported", + "concise": "ERROR: Power management unsupported" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "OK - repeat is now OFF", + "concise": "OK - repeat OFF" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "OK - repeat is now ON", + "concise": "OK - repeat ON" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "OK - clock set: %02d:%02d - %d/%d/%d UTC", + "concise": "OK - clock: %02d:%02d - %d/%d/%d UTC" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "OK - interval rounded to %d", + "concise": "OK - rounded interval %d" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "radio settings could not be saved", + "concise": "radio settings not saved" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "unsupported on this platform", + "concise": "platform unsupported" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "OK - LoRa FEM RX gain on", + "concise": "OK - FEM RX gain on" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "OK - LoRa FEM RX gain off", + "concise": "OK - FEM RX gain off" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "OK - LoRa FEM TX gain on", + "concise": "OK - FEM TX gain on" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "OK - LoRa FEM TX gain off", + "concise": "OK - FEM TX gain off" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "ERROR: range is %lu-%lu us", + "concise": "ERROR: range %lu-%lu us" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, argument too long", + "concise": "Error, arg too long" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, SNR must be numeric", + "concise": "Error, numeric SNR needed" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, invalid frequency", + "concise": "Error, bad frequency" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, use: tempradio f,bw,sf,cr,minutes[,preamble]", + "concise": "Error, tempradio f,bw,sf,cr,minutes[,preamble]" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, use: set radioat f,bw,sf,cr,start (UTC or +minutes)", + "concise": "Error, set radioat f,bw,sf,cr,start (UTC or +minutes)" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, use: set tempradioat f,bw,sf,cr,start,end (UTC or +minutes)", + "concise": "Error, set tempradioat f,bw,sf,cr,start,end (UTC or +minutes)" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, use: set recent.repeater [snr_db]", + "concise": "Error, set recent.repeater [snr_db]" + }, + { + "file": "src/helpers/CommonCLI.cpp", + "verbose": "Error, use: get recent.repeaters [page] | search <2|4|6 hex> [page]", + "concise": "Error, get recent.repeaters [page] | search <2|4|6 hex> [page]" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - blacklist slot must be 1-%u", + "concise": "Err - blacklist slot: 1-%u" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - expected six-digit repeater ID", + "concise": "Err - repeater ID: six hex digits" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - use 1-%d unique six-digit hex IDs separated by commas", + "concise": "Err - use 1-%d unique six-digit hex IDs, comma-separated" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - next blacklist slot is %u", + "concise": "Err - next blacklist slot: %u" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - blacklist range exceeds slot %u", + "concise": "Err - blacklist range past slot %u" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - unable to save flood filter blacklist", + "concise": "Err - flood filter blacklist not saved" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - filter slot must be 1-%d", + "concise": "Err - filter slot: 1-%d" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - packet type must be name, any, 0-15, or 0x00-0x0F", + "concise": "Err - type: name|any|0-15|0x00-0x0F" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - tx timing must be fast or slow", + "concise": "Err - tx: fast|slow" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - scope must be a public name of at most 30 characters", + "concise": "Err - scope: public name, max 30 chars" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - unable to save flood filter", + "concise": "Err - flood filter not saved" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - too many flood filter parameters", + "concise": "Err - excess flood filter parameters" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - require=region requires scope=", + "concise": "Err - require=region needs scope=" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - tx timing requires scope=", + "concise": "Err - tx needs scope=" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - scope slot must be 1-%d", + "concise": "Err - scope slot: 1-%d" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - path bucket must be 1-6", + "concise": "Err - path bucket: 1-6" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - require slot must be 1-%d", + "concise": "Err - require slot: 1-%d" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - channel must be public, #name, or 128/256-bit key", + "concise": "Err - channel: public|#name|128/256-bit key" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - permissions must be 0-255", + "concise": "Err - permissions: 0-255" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - region names cannot contain spaces", + "concise": "Err - region name: no spaces" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - unknown or invalid alert region", + "concise": "Err - unknown/invalid alert region" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - define a usable region before enabling battery alerts", + "concise": "Err - battery alerts need a usable region first" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - multiple narrowest regions; specify one", + "concise": "Err - specify one of the narrowest regions" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - low must be greater than critical", + "concise": "Err - low must exceed critical" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - critical must be less than low", + "concise": "Err - critical must be below low" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "OK - all flood filters removed", + "concise": "OK - flood filters cleared" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - use get guest or set guest on|off", + "concise": "Err - get guest / set guest on|off" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - region load owned by admin/region manager", + "concise": "Err - region load: admin/region manager only" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Error: start is in the past", + "concise": "Error: start in past" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Error: end is in the past", + "concise": "Error: end in past" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Error: end must be after start", + "concise": "Error: end must follow start" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - another remote command is still running", + "concise": "Err - remote command busy" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - remote command is too long", + "concise": "Err - remote command too long" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - filter parameters too long", + "concise": "Err - filter params too long" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - use: set flood.filter[.n] [hops] [path=blacklist] [scope=] [require=region] [tx=slow] [suspend=tempradio]", + "concise": "Err - set flood.filter[.n] [hops] [path=blacklist] [scope=] [require=region] [tx=slow] [suspend=tempradio]" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Error, use: get tempradioat [n]", + "concise": "Error, get tempradioat [n]" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Error, use: get radioat [n]", + "concise": "Error, get radioat [n]" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Error, use: del tempradioat [n]", + "concise": "Error, del tempradioat [n]" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Error, use: del radioat [n]", + "concise": "Error, del radioat [n]" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - use: del flood.filter.blacklist[.<1-%u>| all]", + "concise": "Err - del flood.filter.blacklist[.<1-%u>| all]" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - use: del flood.filter.<1-%d>|all", + "concise": "Err - del flood.filter.<1-%d>|all" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - usage: send text.flood ", + "concise": "Err - send text.flood " + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - usage: set rx.watchdog ", + "concise": "Err - set rx.watchdog " + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - usage: set battery.alert ", + "concise": "Err - set battery.alert " + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - usage: set battery.alert.low <1-100>", + "concise": "Err - set battery.alert.low <1-100>" + }, + { + "file": "examples/simple_repeater/MyMesh.cpp", + "verbose": "Err - usage: set battery.alert.critical <0-99>", + "concise": "Err - set battery.alert.critical <0-99>" + }, + { + "file": "src/helpers/RadioProfileCLI.cpp", + "verbose": "slot must be 1-4 or all", + "concise": "slot: 1-4|all" + }, + { + "file": "src/helpers/RadioProfileCLI.cpp", + "verbose": "future UTC start/end required, within 24 days", + "concise": "future UTC start/end within 24 days" + }, + { + "file": "src/helpers/RadioProfileCLI.cpp", + "verbose": "all four schedule slots occupied", + "concise": "all four slots full" + }, + { + "file": "src/helpers/RadioProfileCLI.cpp", + "verbose": "settings could not be saved", + "concise": "settings not saved" + }, + { + "file": "src/helpers/RadioProfileCLI.cpp", + "verbose": "invalid radio2 command", + "concise": "bad radio2 command" + }, + { + "file": "src/helpers/RadioProfileCLI.cpp", + "verbose": "MediaMesh region unavailable or full", + "concise": "MediaMesh region unavailable/full" + }, + { + "file": "src/helpers/RadioProfileCLI.cpp", + "verbose": "Error: use %s f,bw,sf,cr,rx|rxtx%s[,preamble|auto]", + "concise": "Error: %s f,bw,sf,cr,rx|rxtx%s[,preamble|auto]" + }, + { + "file": "src/helpers/WirelessControl.h", + "verbose": "Error: requested wireless service unavailable in this build/boot", + "concise": "Error: wireless service unavailable this build/boot" + }, + { + "file": "src/helpers/WirelessControl.h", + "verbose": "Error: 2.4ghz is off; use set 2.4ghz on first", + "concise": "Error: 2.4ghz off; set 2.4ghz on first" + }, + { + "file": "src/helpers/WirelessControl.h", + "verbose": "Error: use set wifi/espnow on|off [force]; set 2.4ghz on [all|force]|off [force]", + "concise": "Error: set wifi/espnow on|off [force]; set 2.4ghz on [all|force]|off [force]" + }, + { + "file": "src/helpers/PowerSavingDiagnostics.h", + "verbose": "OK - power diagnostics %s (RAM only; sleep policy unchanged)", + "concise": "OK - power diag %s (RAM only; sleep policy unchanged)" + }, + { + "file": "src/helpers/PowerSavingDiagnostics.h", + "verbose": "OK - power diagnostics cleared; sleep policy unchanged", + "concise": "OK - power diag cleared; sleep policy unchanged" + }, + { + "file": "src/helpers/PowerSavingDiagnostics.h", + "verbose": "Error: use set powersaving.diag on|off", + "concise": "Error: set powersaving.diag on|off" + }, + { + "file": "src/helpers/PowerSavingDiagnostics.h", + "verbose": "Error: use clear powersaving.diag", + "concise": "Error: clear powersaving.diag" + }, + { + "file": "src/helpers/FloodAdvertCLI.h", + "verbose": "ERR: advert limiter unavailable on this role", + "concise": "ERR: advert limiter unavailable for role" + }, + { + "file": "src/helpers/FloodAdvertCLI.h", + "verbose": "ERR: advert list index out of range", + "concise": "ERR: advert index out of range" + }, + { + "file": "src/helpers/FloodAdvertCLI.h", + "verbose": "OK - all advert limit history cleared", + "concise": "OK - all advert history cleared" + }, + { + "file": "src/helpers/FloodAdvertCLI.h", + "verbose": "OK - key advert limit history cleared", + "concise": "OK - key advert history cleared" + }, + { + "file": "src/helpers/LedSettings.cpp", + "verbose": "Error: usage get leds", + "concise": "Error: get leds" + }, + { + "file": "src/helpers/LedSettings.cpp", + "verbose": "Error: usage set leds on|off", + "concise": "Error: set leds on|off" + }, + { + "file": "src/helpers/StorageLayout.cpp", + "verbose": "Error: use get storage.layout", + "concise": "Error: get storage.layout" + } + ], + "noncompact_tokens_sha256": { + "src/helpers/CommonCLI.cpp": "0a3d2e2eabd3e161e352176c4a6a4c4baa184944c08c93b522d4f1ddb5a24edd", + "examples/simple_repeater/MyMesh.cpp": "8567eeef2f327100e397c40b0aaf5b675fcdfea254987437d1ca8d005ad3a6f0", + "src/helpers/RadioProfileCLI.cpp": "d641d16e872eba3e73b9b5a50f9718c10b0b7da72782f793706ac36b3724a624", + "src/helpers/WirelessControl.h": "bbde183f091a8afa7fd65c7b379783f8be2ac4e74ae6037a99a71fde3fb89f1e", + "src/helpers/PowerSavingDiagnostics.h": "18b78f5780046ea8c772c4ce6c337741ae0c027039d9399c94c5f4205d114000", + "src/helpers/FloodAdvertCLI.h": "0f378e4d0f6c3b9b79991068a4b4208139484d2144ee1d7c5094d39043463be4", + "src/helpers/LedSettings.cpp": "df0681234ca1337148ad59caa935857bc692f98bf18603ca6d099c0382028400", + "src/helpers/StorageLayout.cpp": "5dd1c8bcbe2902e1f22d14f9ab425465bdc38926fae23989987d850b484cbb18" + } +} diff --git a/test/fixtures/common_cli_argument_bounds/main.cpp b/test/fixtures/common_cli_argument_bounds/main.cpp index 44a0a4c11..1926a5dec 100644 --- a/test/fixtures/common_cli_argument_bounds/main.cpp +++ b/test/fixtures/common_cli_argument_bounds/main.cpp @@ -106,7 +106,7 @@ static void exercise(const std::string& kind, size_t size, bool advanced, bool a const auto input = command(kind, args); cli.run(input.c_str(), reply.data()); if (size >= COMMON_CLI_TMP_LEN) { - assert(strcmp(reply.data(), "Error, argument too long") == 0); + assert(strcmp(reply.data(), MESH_CLI_REPLY_TEXT("Error, argument too long", "Error, arg too long")) == 0); untouched(cli); for (char value : cli.tmp) assert(value == char(0x55)); } else if (kind == "sensor" || kind == "gps") { @@ -117,7 +117,7 @@ static void exercise(const std::string& kind, size_t size, bool advanced, bool a } else if (kind == "retry") { assert(cli.callbacks.support_calls == 1); assert(cli.saves == (advanced ? 1U : 0U)); - assert(strcmp(reply.data(), advanced ? "OK" : "Error, use on or off on this role") == 0); + assert(strcmp(reply.data(), advanced ? "OK" : MESH_CLI_REPLY_TEXT("Error, use on or off on this role", "Error, on|off")) == 0); if (advanced) { assert(cli.prefs.direct_retry_cr_enabled == 1); assert(cli.prefs.direct_retry_cr4_snr_x4 == 0 && cli.prefs.direct_retry_cr5_snr_x4 == 0); diff --git a/test/test_cli_reply_text.py b/test/test_cli_reply_text.py new file mode 100644 index 000000000..d099ed98d --- /dev/null +++ b/test/test_cli_reply_text.py @@ -0,0 +1,110 @@ +"""Keep constrained CLI prose separate from commands and protocol replies.""" +from pathlib import Path +import configparser +import hashlib +import json +import os +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] +FIXTURE = ROOT / 'test/fixtures/cli_reply_text.json' +SELECTION = re.compile( + r'MESH_CLI_REPLY_TEXT\(("(?:\\.|[^"\\])*"),\s*("(?:\\.|[^"\\])*")\)') +TOKENS = re.compile(r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'|' + r'//[^\n]*|/\*.*?\*/|[A-Za-z_][A-Za-z_0-9]*|[^\s]', re.S) + + +def original_tokens(source): + source = re.sub(r'^#include [<"](?:helpers/)?CLIReplyText\.h[>"]\s*\n', + '', source, flags=re.M) + source = SELECTION.sub(lambda match: match.group(1), source) + return '\n'.join(token.group() for token in TOKENS.finditer(source) + if not token.group().startswith(('//', '/*'))).encode() + + +class CliReplyTextTests(unittest.TestCase): + def test_only_reviewed_human_literals_change_and_control_flow_stays_exact(self): + fixture = json.loads(FIXTURE.read_text()) + self.assertEqual(fixture['schema'], 1) + expected = {} + for row in fixture['messages']: + expected.setdefault(row['file'], set()).add((row['verbose'], row['concise'])) + old, new = row['verbose'], row['concise'] + self.assertLess(len(new), len(old)) + formats = r'%(?:[-+ #0]*[0-9.*]*[a-zA-Z]+|%)' + self.assertEqual(re.findall(formats, old), re.findall(formats, new)) + category = r'^(Error|ERROR|ERR|Err|OK)(?=[: ,\-])' + self.assertEqual(re.findall(category, old), re.findall(category, new)) + self.assertEqual(re.findall(r'\d+(?:\.\d+)?', old), + re.findall(r'\d+(?:\.\d+)?', new)) + self.assertFalse(old.startswith(('> ', '{', 'KEYBACKUP ', 'ACK'))) + self.assertNotRegex(old.lower(), + r'host|replay|password|login|save unconfirmed|reboot|' + r'retained|default|warning|applying in|restore scheduled') + if 'unchanged' in old: + self.assertIn('sleep policy unchanged', old) + self.assertIn('sleep policy unchanged', new) + for file, messages in expected.items(): + source = (ROOT / file).read_text() + actual = {(json.loads(match.group(1)), json.loads(match.group(2))) + for match in SELECTION.finditer(source)} + self.assertEqual(actual, messages, file) + # Substituting each original literal recovers every original C++ + # token, including command tests, branches, calls and arguments. + self.assertEqual(hashlib.sha256(original_tokens(source)).hexdigest(), + fixture['noncompact_tokens_sha256'][file], file) + + def test_profile_activation_is_explicit_and_default_is_off(self): + fixture = json.loads(FIXTURE.read_text()) + enabled = [] + for path in [ROOT / 'platformio.ini', *sorted((ROOT / 'variants').rglob('*.ini'))]: + config = configparser.ConfigParser(interpolation=None, strict=False) + config.read(path) + for section in config.sections(): + if re.search(r'-D\s*MESH_CLI_COMPACT_REPLIES=1\b', + config[section].get('build_flags', '')): + self.assertTrue(section.startswith('env:'), str(path)) + enabled.append(section[4:]) + self.assertEqual(sorted(enabled), sorted(fixture['profiles'])) + header = (ROOT / 'src/helpers/CLIReplyText.h').read_text() + self.assertIn('#ifndef MESH_CLI_COMPACT_REPLIES\n' + '#define MESH_CLI_COMPACT_REPLIES 0', header) + + def test_actual_header_selects_exact_text_without_a_runtime_lookup(self): + compiler = os.environ.get('CXX') or shutil.which('g++') or shutil.which('clang++') + if not compiler: + self.skipTest('host C++ compiler required') + messages = json.loads(FIXTURE.read_text())['messages'] + selections = ',\n'.join('MESH_CLI_REPLY_TEXT(%s, %s)' % + (json.dumps(row['verbose']), json.dumps(row['concise'])) + for row in messages) + source = ('#include \n#include \n' + 'int main(){const char* text[]={'+selections+'};' + 'for(const char* line:text)puts(line);}\n') + with tempfile.TemporaryDirectory(prefix='cli-reply-text-') as folder: + work = Path(folder) + (work / 'test.cpp').write_text(source) + for flag, key in ((None, 'verbose'), (0, 'verbose'), (1, 'concise')): + flags = [] if flag is None else ['-DMESH_CLI_COMPACT_REPLIES='+str(flag)] + built = subprocess.run([compiler, '-std=c++11', '-Os', '-Wall', '-Wextra', + '-Werror', *flags, '-I'+str(ROOT / 'src/helpers'), + str(work / 'test.cpp'), '-o', str(work / 'test')], capture_output=True) + self.assertEqual(built.returncode, 0, built.stderr.decode(errors='replace')) + result = subprocess.run([str(work / 'test')], capture_output=True) + self.assertEqual(result.returncode, 0, result.stderr.decode(errors='replace')) + self.assertEqual(result.stdout.decode().splitlines(), + [row[key] for row in messages]) + if flag == 1: + image = (work / 'test').read_bytes() + selected = {row['concise'] for row in messages} + for row in messages: + if row['verbose'] not in selected: + self.assertNotIn(row['verbose'].encode()+b'\0', image) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_common_cli_argument_bounds.py b/test/test_common_cli_argument_bounds.py index d0d762a1f..8f56e9af9 100644 --- a/test/test_common_cli_argument_bounds.py +++ b/test/test_common_cli_argument_bounds.py @@ -54,12 +54,15 @@ class CommonCLIArgumentBoundsTests(unittest.TestCase): cls.work = tempfile.TemporaryDirectory(prefix="meshcore-cli-argument-bounds-") cls.binaries = {} for name, defines in (("default", []), ("larger", ["-DCOMMON_CLI_TMP_LEN=192"]), + ("compact", ["-DMESH_CLI_COMPACT_REPLIES=1"]), ("legacy", [])): generated = source if name == "legacy": # Restore all three audited unchecked copies as a negative control. + error = (r'(?:"Error, argument too long"|MESH_CLI_REPLY_TEXT\(' + r'"Error, argument too long", "Error, arg too long"\))') pattern = (r'if \(!copyCLIArgument\(tmp, sizeof\(tmp\), (&(?:command|config)\[\d+\])\)\) ' - r'\{\s*strcpy\(reply, "Error, argument too long"\);\s*return;\s*\}') + r'\{\s*strcpy\(reply, ' + error + r'\);\s*return;\s*\}') generated, count = re.subn(pattern, r"strcpy(tmp, \1);", generated) assert count == 3, "review the negative controls after branch changes" directory = Path(cls.work.name) @@ -86,7 +89,7 @@ class CommonCLIArgumentBoundsTests(unittest.TestCase): env=dict(os.environ, ASAN_OPTIONS="detect_leaks=0:abort_on_error=0")) def test_production_boundaries_and_rejection_without_effects(self): - for variant in ("default", "larger"): + for variant in ("default", "larger", "compact"): with self.subTest(variant=variant): result = self.run_case(variant, "production") self.assertEqual(result.returncode, 0, result.stdout + result.stderr) diff --git a/variants/wio-e5-mini/platformio.ini b/variants/wio-e5-mini/platformio.ini index 60c5a6d51..49d6a84b2 100644 --- a/variants/wio-e5-mini/platformio.ini +++ b/variants/wio-e5-mini/platformio.ini @@ -60,6 +60,7 @@ build_flags = ${lora_e5_mini.build_flags} -D MESH_ENABLE_CLOCK_SYNC=0 ; preserve the 16 KiB filesystem on this 256 KiB part -D MESH_ENABLE_FLOOD_GROUP_MODERATION=0 ; keep core routing and radio scheduling in flash -D MESH_PACKET_LOGGING_COMPACT=1 ; retain complete RX/TX bytes without the duplicate parsed RX line + -D MESH_CLI_COMPACT_REPLIES=1 ; concise human prose; command/protocol replies stay intact ; User-approved Mini Repeater cuts only; other boards keep their defaults. -D MESH_ENABLE_CARRIER_TOOLS=0 -D MESH_ENABLE_MANAGEMENT_REPORTING=0