From fb518f5f1d6e8b50b398cfbd030ea9260fccfd1f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Jan 2026 08:19:46 +0000 Subject: [PATCH 1/8] Add configurable packet type filtering for repeaters Large mesh networks suffer from airtime congestion when repeaters forward all packet types indiscriminately. This commit implements granular packet filtering to allow network operators to selectively control which packet types their repeaters forward, reducing airtime consumption on congested networks. Based on community feedback, we optimized the approach to eliminate ~400 bytes of code duplication and added CLI integration. Core Implementation: - New header src/helpers/PacketTypeNames.h - Centralized packet/advert type name definitions - New preferences (offset 170-172): - uint16_t repeat_packet_types - Bitmask: bit=1 means allow repeat (default: 0xFFFF) - uint8_t repeat_advert_types - Bitmask: bit=1 means allow advert sub-type (default: 0xFFFF) - Enhanced filtering in allowPacketForward() - Check packet type first, then advert sub-type for adverts - CLI commands - get repeat, set repeat on/off, set repeat advert. on/off Design Decisions: - Positive logic: bit=1 means "allow" (intuitive: set repeat on sets bit to 1) - Permissive default: 0xFFFF repeats everything (backward compatible) - Two-level advert filtering: Block all adverts, then selectively allow sub-types (chat, sensor, room, repeater) Backward Compatibility: - Migrates disable_fwd=true from old firmware to repeat_packet_types=0x0000 - Keeps disable_fwd synchronized for downgrade safety CLI Reference: Display Repeat Configuration: get repeat Show which packet types are being repeated get repeat advert Show which advert sub-types are being repeated Control Packet Repeat: set repeat {on|off} Enable/disable all packet types set repeat {req|resp|txt|ack|advert|grp.txt|grp.data|anon|path|trace|multi|control|raw} {on|off} Enable/disable specific packet type set repeat advert.{none|chat|repeater|room|sensor} {on|off} Enable/disable specific advert sub-type (only affects packets when 'advert' type is filtered) Examples: # Stop repeating group messages set repeat grp.txt off set repeat grp.data off # Block all adverts, then allow only chat and sensor set repeat advert off set repeat advert.chat on set repeat advert.sensor on # Check which advert sub-types are enabled get repeat advert # Emergency: stop repeating everything set repeat off # Resume normal operation set repeat on Notes: - Default: All packet types repeat (0xFFFF) - Setting 'repeat advert off' also clears all advert sub-type bits - Setting 'repeat advert on' also sets all advert sub-type bits --- examples/simple_repeater/MyMesh.cpp | 39 +++++- examples/simple_repeater/MyMesh.h | 1 + src/helpers/CommonCLI.cpp | 207 ++++++++++++++++++++++++++-- src/helpers/CommonCLI.h | 2 + src/helpers/PacketTypeNames.h | 74 ++++++++++ 5 files changed, 314 insertions(+), 9 deletions(-) create mode 100644 src/helpers/PacketTypeNames.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 33e32a68a..d6091e159 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -318,12 +318,45 @@ File MyMesh::openAppend(const char *fname) { } bool MyMesh::allowPacketForward(const mesh::Packet *packet) { - if (_prefs.disable_fwd) return false; if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; if (packet->isRouteFlood() && recv_pkt_region == NULL) { MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); return false; } + + // Check if this packet type is allowed to be repeated + uint8_t pkt_type = packet->getPayloadType(); + if (pkt_type < mesh::MAX_PACKET_TYPES && (_prefs.repeat_packet_types & (1 << pkt_type)) == 0) { + // Packet type is not allowed to repeat + MESH_DEBUG_PRINTLN("allowPacketForward: packet type %d not allowed to repeat", pkt_type); + return false; + } + + // For advert packets, also check advert sub-type filtering + if (pkt_type == PAYLOAD_TYPE_ADVERT) { + const int app_data_offset = PUB_KEY_SIZE + 4 + SIGNATURE_SIZE; + + // Quick sanity check before parsing + if (packet->payload_len > app_data_offset) { + const uint8_t* app_data = &packet->payload[app_data_offset]; + int app_data_len = packet->payload_len - app_data_offset; + + // Only parse if there's actual data + if (app_data_len > 0) { + // Quick check: read advert type from flags byte (avoid full parse if possible) + uint8_t flags = app_data[0]; + uint8_t adv_type = flags & 0x0F; + + // Check if this advert sub-type is allowed + if (adv_type < mesh::MAX_ADVERT_TYPES && (_prefs.repeat_advert_types & (1 << adv_type)) == 0) { + MESH_DEBUG_PRINTLN("allowPacketForward: %s advert type not allowed to repeat", + mesh::getAdvertTypeName(adv_type)); + return false; + } + } + } + } + return true; } @@ -723,6 +756,10 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.advert_loc_policy = ADVERT_LOC_PREFS; _prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier + + // Packet filtering defaults - repeat everything by default + _prefs.repeat_packet_types = 0xFFFF; // Repeat all packet types by default + _prefs.repeat_advert_types = 0xFFFF; // Repeat all advert types by default } void MyMesh::begin(FILESYSTEM *fs) { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 343aa44f5..50bc048d9 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 78e1b5e0b..3ed9460c3 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -2,6 +2,7 @@ #include "CommonCLI.h" #include "TxtDataHelpers.h" #include "AdvertDataHelpers.h" +#include "PacketTypeNames.h" #include // Believe it or not, this std C function is busted on some platforms! @@ -72,7 +73,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 - // 170 + file.read((uint8_t *)&_prefs->repeat_packet_types, sizeof(_prefs->repeat_packet_types)); // 170 + file.read((uint8_t *)&_prefs->repeat_advert_types, sizeof(_prefs->repeat_advert_types)); // 172 + // 173 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -99,7 +102,25 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1); _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2); + // Validate packet repeat bitmask - ensure no bits set beyond valid packet types + uint16_t valid_mask = (1 << mesh::MAX_PACKET_TYPES) - 1; + _prefs->repeat_packet_types &= valid_mask; + + // Validate advert repeat bitmask - ensure no bits set beyond valid advert types + uint8_t valid_adv_mask = (1 << mesh::MAX_ADVERT_TYPES) - 1; + _prefs->repeat_advert_types &= valid_adv_mask; + file.close(); + + // Migrate legacy disable_fwd to new granular filtering system + if (_prefs->disable_fwd && _prefs->repeat_packet_types == 0xFFFF) { + // Old firmware had disable_fwd=true but new filtering not configured yet + // Migrate: clear all bits to block all packet types (bit=1 means allow) + _prefs->repeat_packet_types = 0x0000; // Block all packet types + savePrefs(); // Persist migration immediately + } + // Note: We keep disable_fwd in sync with filtering state for downgrade compatibility + // If repeat_packet_types != 0xFFFF (any filtering), keep disable_fwd=true for old firmware } } @@ -155,7 +176,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 - // 170 + file.write((uint8_t *)&_prefs->repeat_packet_types, sizeof(_prefs->repeat_packet_types)); // 170 + file.write((uint8_t *)&_prefs->repeat_advert_types, sizeof(_prefs->repeat_advert_types)); // 172 + // 173 file.close(); } @@ -282,8 +305,70 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %s", tmp); } else if (memcmp(config, "name", 4) == 0) { sprintf(reply, "> %s", _prefs->node_name); - } else if (memcmp(config, "repeat", 6) == 0) { - sprintf(reply, "> %s", _prefs->disable_fwd ? "off" : "on"); + } else if (memcmp(config, "repeat advert", 13) == 0 && (config[13] == 0 || config[13] == ' ')) { + // Show which advert sub-types are allowed to repeat + reply[0] = '>'; + reply[1] = ' '; + int pos = 2; + bool has_any = false; + + for (int i = 0; i < mesh::MAX_ADVERT_TYPES; i++) { + if ((_prefs->repeat_advert_types & (1 << i)) != 0) { + const char* name = mesh::getAdvertTypeName(i); + int len = strlen(name); + + if (pos + (has_any ? 1 : 0) + len + 1 >= 160) { + strcpy(&reply[pos], "..."); + pos += 3; + break; + } + + if (has_any) { + reply[pos++] = ','; + } + strcpy(&reply[pos], name); + pos += len; + has_any = true; + } + } + + if (!has_any) { + strcpy(&reply[2], "all filtered"); + } else { + reply[pos] = 0; + } + } else if (memcmp(config, "repeat", 6) == 0 && (config[6] == 0 || config[6] == ' ')) { + // Show which packet types are being repeated + reply[0] = '>'; + reply[1] = ' '; + int pos = 2; + bool has_any = false; + + for (int i = 0; i < mesh::MAX_PACKET_TYPES; i++) { + if ((_prefs->repeat_packet_types & (1 << i)) != 0) { // If bit set, it's being repeated + const char* name = mesh::getPacketTypeName(i); + int len = strlen(name); + + if (pos + (has_any ? 1 : 0) + len + 1 >= 160) { + strcpy(&reply[pos], "..."); + pos += 3; + break; + } + + if (has_any) { + reply[pos++] = ','; + } + strcpy(&reply[pos], name); + pos += len; + has_any = true; + } + } + + if (!has_any) { + strcpy(&reply[2], "all filtered"); + } else { + reply[pos] = 0; + } } else if (memcmp(config, "lat", 3) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat)); } else if (memcmp(config, "lon", 3) == 0) { @@ -413,10 +498,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); savePrefs(); strcpy(reply, "OK"); - } else if (memcmp(config, "repeat ", 7) == 0) { - _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0; - savePrefs(); - strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON"); } else if (memcmp(config, "radio ", 6) == 0) { strcpy(tmp, &config[6]); const char *parts[4]; @@ -550,6 +631,116 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->adc_multiplier = 0.0f; strcpy(reply, "Error: unsupported by this board"); }; + } else if (memcmp(config, "repeat ", 7) == 0) { + const char* rest = &config[7]; + + // Check for advert sub-type commands: "repeat advert.sensor on/off" + if (memcmp(rest, "advert.", 7) == 0) { + const char* adv_type_and_state = &rest[7]; + // Find space separating type from on/off + const char* space_pos = strchr(adv_type_and_state, ' '); + if (space_pos != NULL) { + int type_len = space_pos - adv_type_and_state; + char adv_type_name[16]; + if (type_len < 16) { + memcpy(adv_type_name, adv_type_and_state, type_len); + adv_type_name[type_len] = 0; + + const char* state = space_pos + 1; + int adv_type_idx = mesh::findAdvertTypeByName(adv_type_name); + + if (adv_type_idx >= 0) { + if (memcmp(state, "on", 2) == 0) { + _prefs->repeat_advert_types |= (1 << adv_type_idx); + savePrefs(); + sprintf(reply, "OK - %s adverts will be repeated", adv_type_name); + } else if (memcmp(state, "off", 3) == 0) { + _prefs->repeat_advert_types &= ~(1 << adv_type_idx); + savePrefs(); + sprintf(reply, "OK - %s adverts will not be repeated", adv_type_name); + } else { + strcpy(reply, "Error: use 'on' or 'off'"); + } + } else { + strcpy(reply, "Error: unknown advert type (use: none,chat,repeater,room,sensor)"); + } + } else { + strcpy(reply, "Error: advert type name too long"); + } + } else { + strcpy(reply, "Error: format is 'set repeat advert. on/off'"); + } + } + // Check for packet type commands: "repeat advert on/off", "repeat grp.txt on/off" + else { + // Find space separating type from on/off + const char* space_pos = strchr(rest, ' '); + if (space_pos != NULL) { + int type_len = space_pos - rest; + char type_name[16]; + if (type_len < 16) { + memcpy(type_name, rest, type_len); + type_name[type_len] = 0; + + const char* state = space_pos + 1; + int type_idx = mesh::findPacketTypeByName(type_name); + + if (type_idx >= 0) { + if (memcmp(state, "on", 2) == 0) { + _prefs->repeat_packet_types |= (1 << type_idx); // Set bit = repeat ON + + // Special handling for advert: enable all advert sub-types + if (type_idx == PAYLOAD_TYPE_ADVERT) { + _prefs->repeat_advert_types = (1 << mesh::MAX_ADVERT_TYPES) - 1; // All bits set + } + + // Sync disable_fwd for downgrade compatibility + _prefs->disable_fwd = (_prefs->repeat_packet_types != 0xFFFF); + + savePrefs(); + sprintf(reply, "OK - %s packets will be repeated", type_name); + } else if (memcmp(state, "off", 3) == 0) { + _prefs->repeat_packet_types &= ~(1 << type_idx); // Clear bit = repeat OFF + + // Special handling for advert: clear all advert sub-types + if (type_idx == PAYLOAD_TYPE_ADVERT) { + _prefs->repeat_advert_types = 0x00; // All bits clear + } + + // Sync disable_fwd for downgrade compatibility + _prefs->disable_fwd = (_prefs->repeat_packet_types != 0xFFFF); + + savePrefs(); + if (type_idx == PAYLOAD_TYPE_ADVERT) { + sprintf(reply, "OK - %s packets will not be repeated (use 'set repeat advert. on' for exceptions)", type_name); + } else { + sprintf(reply, "OK - %s packets will not be repeated", type_name); + } + } else { + strcpy(reply, "Error: use 'on' or 'off'"); + } + } else { + strcpy(reply, "Error: unknown type (use: req,resp,txt,ack,advert,grp.txt,grp.data,anon,path,trace,multi,control,raw)"); + } + } else { + strcpy(reply, "Error: packet type name too long"); + } + } else { + strcpy(reply, "Error: format is 'set repeat on/off'"); + } + } + } else if (memcmp(config, "repeat on", 9) == 0) { + // Turn all packet types ON (set all bits) + _prefs->repeat_packet_types = 0xFFFF; + _prefs->disable_fwd = false; // Sync for downgrade compatibility + savePrefs(); + strcpy(reply, "OK - all packet types will be repeated"); + } else if (memcmp(config, "repeat off", 10) == 0) { + // Turn all packet types OFF (clear all bits) + _prefs->repeat_packet_types = 0x0000; + _prefs->disable_fwd = true; // Sync for downgrade compatibility + savePrefs(); + strcpy(reply, "OK - all packet types will not be repeated"); } else { sprintf(reply, "unknown config: %s", config); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 642a4cce3..c1daa3132 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -50,6 +50,8 @@ struct NodePrefs { // persisted to file uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; float adc_multiplier; + uint16_t repeat_packet_types; // Bitmask: bit=1 means ALLOW repeat (0-15) + uint8_t repeat_advert_types; // Bitmask: bit=1 means ALLOW advert type through }; class CommonCLICallbacks { diff --git a/src/helpers/PacketTypeNames.h b/src/helpers/PacketTypeNames.h new file mode 100644 index 000000000..801d995f6 --- /dev/null +++ b/src/helpers/PacketTypeNames.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +namespace mesh { + +constexpr int MAX_PACKET_TYPES = 16; +constexpr int MAX_ADVERT_TYPES = 5; + +// Packet type name strings - stored in flash memory +// These correspond to PAYLOAD_TYPE_* values 0x00 through 0x0F +static const char* const PACKET_TYPE_NAMES[MAX_PACKET_TYPES] = { + "req", // 0x00 PAYLOAD_TYPE_REQ + "resp", // 0x01 PAYLOAD_TYPE_RESPONSE + "txt", // 0x02 PAYLOAD_TYPE_TXT_MSG + "ack", // 0x03 PAYLOAD_TYPE_ACK + "advert", // 0x04 PAYLOAD_TYPE_ADVERT + "grp.txt", // 0x05 PAYLOAD_TYPE_GRP_TXT + "grp.data", // 0x06 PAYLOAD_TYPE_GRP_DATA + "anon", // 0x07 PAYLOAD_TYPE_ANON_REQ + "path", // 0x08 PAYLOAD_TYPE_PATH + "trace", // 0x09 PAYLOAD_TYPE_TRACE + "multi", // 0x0A PAYLOAD_TYPE_MULTIPART + "control", // 0x0B PAYLOAD_TYPE_CONTROL + "12", // 0x0C (reserved) + "13", // 0x0D (reserved) + "14", // 0x0E (reserved) + "raw" // 0x0F PAYLOAD_TYPE_RAW_CUSTOM +}; + +// Advert type name strings - stored in flash memory +// These correspond to ADV_TYPE_* values 0 through 4 +static const char* const ADVERT_TYPE_NAMES[MAX_ADVERT_TYPES] = { + "none", // 0 ADV_TYPE_NONE + "chat", // 1 ADV_TYPE_CHAT (companion radios) + "repeater", // 2 ADV_TYPE_REPEATER + "room", // 3 ADV_TYPE_ROOM (room servers) + "sensor" // 4 ADV_TYPE_SENSOR +}; + +// Helper function to safely get packet type name +inline const char* getPacketTypeName(uint8_t type) { + if (type >= MAX_PACKET_TYPES) return "unknown"; + return PACKET_TYPE_NAMES[type]; +} + +// Helper function to find packet type index by name +inline int findPacketTypeByName(const char* name) { + for (int i = 0; i < MAX_PACKET_TYPES; i++) { + if (strcmp(name, PACKET_TYPE_NAMES[i]) == 0) { + return i; + } + } + return -1; +} + +// Helper function to safely get advert type name +inline const char* getAdvertTypeName(uint8_t type) { + if (type >= MAX_ADVERT_TYPES) return "unknown"; + return ADVERT_TYPE_NAMES[type]; +} + +// Helper function to find advert type index by name +inline int findAdvertTypeByName(const char* name) { + for (int i = 0; i < MAX_ADVERT_TYPES; i++) { + if (strcmp(name, ADVERT_TYPE_NAMES[i]) == 0) { + return i; + } + } + return -1; +} + +} From 8f9d1b6b0bc249504c762f13a0cd000af0a49bb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Jan 2026 12:04:37 +0000 Subject: [PATCH 2/8] Add hop count filtering for advert packets Add ability to filter advert packets based on hop count to reduce propagation of distant node advertisements and save airtime. Changes: - New preference field: advert_max_hops (uint8_t at offset 173) - 0 = no limit (default) - 1-255 = maximum hops allowed for advert packets - CLI commands: - get repeat advert.max_hops - Display current max hops setting - set repeat advert.max_hops - Set max hops (0-255) - Filtering logic in allowPacketForward(): - Checks packet->path_len against advert_max_hops for advert packets - Only applies when advert_max_hops > 0 Use cases: - Reduce advert spam from distant nodes - Create local-only discovery zones - Save airtime on large networks Examples: # Only allow adverts from nodes within 3 hops set repeat advert.max_hops 3 # Allow adverts from immediate neighbors only set repeat advert.max_hops 1 # Disable hop filtering (default) set repeat advert.max_hops 0 # Check current setting get repeat advert.max_hops --- examples/simple_repeater/MyMesh.cpp | 8 ++++ src/helpers/CommonCLI.cpp | 68 +++++++++++++++++++++-------- src/helpers/CommonCLI.h | 1 + 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d6091e159..2baa1adcd 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -355,6 +355,13 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) { } } } + + // Check max hops for advert packets (0 = no limit) + if (_prefs.advert_max_hops > 0 && packet->path_len > _prefs.advert_max_hops) { + MESH_DEBUG_PRINTLN("allowPacketForward: advert exceeded max hops (%d > %d)", + packet->path_len, _prefs.advert_max_hops); + return false; + } } return true; @@ -760,6 +767,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc // Packet filtering defaults - repeat everything by default _prefs.repeat_packet_types = 0xFFFF; // Repeat all packet types by default _prefs.repeat_advert_types = 0xFFFF; // Repeat all advert types by default + _prefs.advert_max_hops = 0; // No hop limit by default } void MyMesh::begin(FILESYSTEM *fs) { diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 3ed9460c3..94b653d16 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -75,7 +75,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 file.read((uint8_t *)&_prefs->repeat_packet_types, sizeof(_prefs->repeat_packet_types)); // 170 file.read((uint8_t *)&_prefs->repeat_advert_types, sizeof(_prefs->repeat_advert_types)); // 172 - // 173 + file.read((uint8_t *)&_prefs->advert_max_hops, sizeof(_prefs->advert_max_hops)); // 173 + // 174 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -110,6 +111,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { uint8_t valid_adv_mask = (1 << mesh::MAX_ADVERT_TYPES) - 1; _prefs->repeat_advert_types &= valid_adv_mask; + // Validate advert max hops (0 = no limit, otherwise 1-255) + // No validation needed since uint8_t naturally constrains to 0-255 + file.close(); // Migrate legacy disable_fwd to new granular filtering system @@ -178,7 +182,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 file.write((uint8_t *)&_prefs->repeat_packet_types, sizeof(_prefs->repeat_packet_types)); // 170 file.write((uint8_t *)&_prefs->repeat_advert_types, sizeof(_prefs->repeat_advert_types)); // 172 - // 173 + file.write((uint8_t *)&_prefs->advert_max_hops, sizeof(_prefs->advert_max_hops)); // 173 + // 174 file.close(); } @@ -305,6 +310,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %s", tmp); } else if (memcmp(config, "name", 4) == 0) { sprintf(reply, "> %s", _prefs->node_name); + } else if (memcmp(config, "repeat advert.max_hops", 22) == 0) { + // Show max hops for advert packets + if (_prefs->advert_max_hops == 0) { + sprintf(reply, "> unlimited"); + } else { + sprintf(reply, "> %d", _prefs->advert_max_hops); + } } else if (memcmp(config, "repeat advert", 13) == 0 && (config[13] == 0 || config[13] == ' ')) { // Show which advert sub-types are allowed to repeat reply[0] = '>'; @@ -634,22 +646,41 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "repeat ", 7) == 0) { const char* rest = &config[7]; - // Check for advert sub-type commands: "repeat advert.sensor on/off" + // Check for advert sub-type commands: "repeat advert.sensor on/off" or "repeat advert.max_hops " if (memcmp(rest, "advert.", 7) == 0) { const char* adv_type_and_state = &rest[7]; - // Find space separating type from on/off - const char* space_pos = strchr(adv_type_and_state, ' '); - if (space_pos != NULL) { - int type_len = space_pos - adv_type_and_state; - char adv_type_name[16]; - if (type_len < 16) { - memcpy(adv_type_name, adv_type_and_state, type_len); - adv_type_name[type_len] = 0; - - const char* state = space_pos + 1; - int adv_type_idx = mesh::findAdvertTypeByName(adv_type_name); - if (adv_type_idx >= 0) { + // Special handling for max_hops + if (memcmp(adv_type_and_state, "max_hops ", 9) == 0) { + const char* value_str = &adv_type_and_state[9]; + int value = atoi(value_str); + if (value < 0 || value > 255) { + strcpy(reply, "Error: max_hops must be 0-255 (0 = unlimited)"); + } else { + _prefs->advert_max_hops = (uint8_t)value; + savePrefs(); + if (value == 0) { + strcpy(reply, "OK - advert max hops set to unlimited"); + } else { + sprintf(reply, "OK - advert max hops set to %d", value); + } + } + } + // Handle advert sub-type on/off commands + else { + // Find space separating type from on/off + const char* space_pos = strchr(adv_type_and_state, ' '); + if (space_pos != NULL) { + int type_len = space_pos - adv_type_and_state; + char adv_type_name[16]; + if (type_len < 16) { + memcpy(adv_type_name, adv_type_and_state, type_len); + adv_type_name[type_len] = 0; + + const char* state = space_pos + 1; + int adv_type_idx = mesh::findAdvertTypeByName(adv_type_name); + + if (adv_type_idx >= 0) { if (memcmp(state, "on", 2) == 0) { _prefs->repeat_advert_types |= (1 << adv_type_idx); savePrefs(); @@ -664,11 +695,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { strcpy(reply, "Error: unknown advert type (use: none,chat,repeater,room,sensor)"); } + } else { + strcpy(reply, "Error: advert type name too long"); + } } else { - strcpy(reply, "Error: advert type name too long"); + strcpy(reply, "Error: format is 'set repeat advert. on/off'"); } - } else { - strcpy(reply, "Error: format is 'set repeat advert. on/off'"); } } // Check for packet type commands: "repeat advert on/off", "repeat grp.txt on/off" diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index c1daa3132..05c1c0fab 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -52,6 +52,7 @@ struct NodePrefs { // persisted to file float adc_multiplier; uint16_t repeat_packet_types; // Bitmask: bit=1 means ALLOW repeat (0-15) uint8_t repeat_advert_types; // Bitmask: bit=1 means ALLOW advert type through + uint8_t advert_max_hops; // Max hops for advert packets (0 = no limit) }; class CommonCLICallbacks { From 9b0d8646ab9d84ecd27fcff12ef1013d85fe2d3c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Jan 2026 17:24:49 +0000 Subject: [PATCH 3/8] Fix 'set repeat on/off' command order The 'set repeat on' and 'set repeat off' commands were not working because they were being caught by the 'repeat on/off' handler first. Issue: memcmp(config, 'repeat ', 7) at line 654 matches 'repeat on' before the specific 'repeat on' handler at line 760 could be reached. Fix: Check for 'repeat on' and 'repeat off' BEFORE checking for 'repeat ' with trailing space. This ensures the global on/off commands are handled correctly before attempting to parse packet type names. Changes: - Moved 'repeat on' handler before 'repeat ' handler - Moved 'repeat off' handler before 'repeat ' handler - Removed duplicate handlers that were unreachable --- src/helpers/CommonCLI.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 94b653d16..97ffcdd7f 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -643,6 +643,18 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->adc_multiplier = 0.0f; strcpy(reply, "Error: unsupported by this board"); }; + } else if (memcmp(config, "repeat on", 9) == 0) { + // Turn all packet types ON (set all bits) + _prefs->repeat_packet_types = 0xFFFF; + _prefs->disable_fwd = false; // Sync for downgrade compatibility + savePrefs(); + strcpy(reply, "OK - all packet types will be repeated"); + } else if (memcmp(config, "repeat off", 10) == 0) { + // Turn all packet types OFF (clear all bits) + _prefs->repeat_packet_types = 0x0000; + _prefs->disable_fwd = true; // Sync for downgrade compatibility + savePrefs(); + strcpy(reply, "OK - all packet types will not be repeated"); } else if (memcmp(config, "repeat ", 7) == 0) { const char* rest = &config[7]; @@ -761,18 +773,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "Error: format is 'set repeat on/off'"); } } - } else if (memcmp(config, "repeat on", 9) == 0) { - // Turn all packet types ON (set all bits) - _prefs->repeat_packet_types = 0xFFFF; - _prefs->disable_fwd = false; // Sync for downgrade compatibility - savePrefs(); - strcpy(reply, "OK - all packet types will be repeated"); - } else if (memcmp(config, "repeat off", 10) == 0) { - // Turn all packet types OFF (clear all bits) - _prefs->repeat_packet_types = 0x0000; - _prefs->disable_fwd = true; // Sync for downgrade compatibility - savePrefs(); - strcpy(reply, "OK - all packet types will not be repeated"); } else { sprintf(reply, "unknown config: %s", config); } From 8e73270359b3c91ce0f8f9d020fa36dc278bf813 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Jan 2026 20:16:35 +0000 Subject: [PATCH 4/8] Add flood-mode guard to advert hop count filtering Add isRouteFlood() check to advert hop count filtering for defensive programming. While adverts are typically flood packets, path_len is only meaningful for flood-mode packets (not direct-mode), so we should guard against edge cases where a direct-mode advert might be forwarded. This makes the hop count check consistent with the other flood-related checks at the top of allowPacketForward(). --- examples/simple_repeater/MyMesh.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 2baa1adcd..37d6917da 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -356,8 +356,9 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) { } } - // Check max hops for advert packets (0 = no limit) - if (_prefs.advert_max_hops > 0 && packet->path_len > _prefs.advert_max_hops) { + // Check max hops for flood-mode advert packets (0 = no limit) + // path_len is only meaningful for flood packets, not direct-mode packets + if (packet->isRouteFlood() && _prefs.advert_max_hops > 0 && packet->path_len > _prefs.advert_max_hops) { MESH_DEBUG_PRINTLN("allowPacketForward: advert exceeded max hops (%d > %d)", packet->path_len, _prefs.advert_max_hops); return false; From 83c0b4491ebbb86400433b251fa44cf3deea3da3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Jan 2026 20:39:36 +0000 Subject: [PATCH 5/8] Fix advert sub-type filtering: auto-enable packet type Bug: When users ran: set repeat advert off set repeat advert.chat on The chat sub-type bit would be set, but adverts would still be blocked because the advert packet type bit remained off. The filtering logic in allowPacketForward() checks packet type BEFORE sub-type, so if the packet type bit is off, sub-type filtering is never reached. Fix: When enabling any advert sub-type, automatically enable the advert packet type bit if it's currently off. This matches user expectations and makes the CLI more intuitive. Also syncs disable_fwd for backward compatibility. Corrected Documentation: Advert sub-type filtering requires the 'advert' packet type to be enabled. When you enable a specific sub-type (e.g., 'set repeat advert.chat on'), the advert packet type is automatically enabled if needed. Examples: # Block all adverts, then allow only chat and sensor set repeat advert off # Blocks all adverts set repeat advert.chat on # Auto-enables advert type, allows chat set repeat advert.sensor on # Allows sensor (advert type already on) # Fine-tune: block repeater adverts but allow everything else set repeat on # Enable all packet types set repeat advert.repeater off # Block only repeater adverts # Check configuration get repeat # Show packet types get repeat advert # Show advert sub-types Note: 'set repeat advert off' clears all sub-type bits AND the packet type bit. 'set repeat advert on' sets all sub-type bits AND the packet type bit. --- src/helpers/CommonCLI.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 97ffcdd7f..4ac94ab44 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -695,6 +695,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch if (adv_type_idx >= 0) { if (memcmp(state, "on", 2) == 0) { _prefs->repeat_advert_types |= (1 << adv_type_idx); + + // Automatically enable advert packet type if it's off + if ((_prefs->repeat_packet_types & (1 << PAYLOAD_TYPE_ADVERT)) == 0) { + _prefs->repeat_packet_types |= (1 << PAYLOAD_TYPE_ADVERT); + } + + // Sync disable_fwd for downgrade compatibility + _prefs->disable_fwd = (_prefs->repeat_packet_types != 0xFFFF); + savePrefs(); sprintf(reply, "OK - %s adverts will be repeated", adv_type_name); } else if (memcmp(state, "off", 3) == 0) { From 432ede986cec5dd8989cf964df0769b3926b9748 Mon Sep 17 00:00:00 2001 From: Michiel Appelman Date: Thu, 8 Jan 2026 12:51:07 +0100 Subject: [PATCH 6/8] Exclude companion adverts from being limited by max_hops. --- examples/simple_repeater/MyMesh.cpp | 20 +++++++++++--------- src/helpers/CommonCLI.cpp | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 37d6917da..5cd143f19 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -332,7 +332,7 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) { return false; } - // For advert packets, also check advert sub-type filtering + // For advert packets, check sub-type filtering and max hops if (pkt_type == PAYLOAD_TYPE_ADVERT) { const int app_data_offset = PUB_KEY_SIZE + 4 + SIGNATURE_SIZE; @@ -353,15 +353,17 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) { mesh::getAdvertTypeName(adv_type)); return false; } - } - } - // Check max hops for flood-mode advert packets (0 = no limit) - // path_len is only meaningful for flood packets, not direct-mode packets - if (packet->isRouteFlood() && _prefs.advert_max_hops > 0 && packet->path_len > _prefs.advert_max_hops) { - MESH_DEBUG_PRINTLN("allowPacketForward: advert exceeded max hops (%d > %d)", - packet->path_len, _prefs.advert_max_hops); - return false; + // Check max hops for flood-mode advert packets (0 = no limit) + // path_len is only meaningful for flood packets, not direct-mode packets + // CHAT adverts are excluded from hop count filtering + if (packet->isRouteFlood() && adv_type != ADV_TYPE_CHAT && _prefs.advert_max_hops > 0 && + packet->path_len > _prefs.advert_max_hops ) { + MESH_DEBUG_PRINTLN("allowPacketForward: advert exceeded max hops (%d > %d)", + packet->path_len, _prefs.advert_max_hops); + return false; + } + } } } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 4ac94ab44..5a76e8527 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -315,7 +315,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch if (_prefs->advert_max_hops == 0) { sprintf(reply, "> unlimited"); } else { - sprintf(reply, "> %d", _prefs->advert_max_hops); + sprintf(reply, "> %d (excluding companion adverts)", _prefs->advert_max_hops); } } else if (memcmp(config, "repeat advert", 13) == 0 && (config[13] == 0 || config[13] == ' ')) { // Show which advert sub-types are allowed to repeat @@ -674,7 +674,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch if (value == 0) { strcpy(reply, "OK - advert max hops set to unlimited"); } else { - sprintf(reply, "OK - advert max hops set to %d", value); + sprintf(reply, "OK - advert max hops set to %d (excluding companion adverts)", value); } } } From bdbe1f678bdb43f3f5b06a5e4edb1f7e5fc6de3c Mon Sep 17 00:00:00 2001 From: Michiel Appelman Date: Thu, 8 Jan 2026 22:23:33 +0100 Subject: [PATCH 7/8] Reinstate disable_fwd as global on/off switch The disable_fwd preference now acts as a global repeating on/off switch that is independent of the packet filter bitmask. This allows users to configure granular packet filtering preferences and then toggle repeating on/off without losing their custom settings. Changes: - allowPacketForward() now checks disable_fwd as the first filter - 'set repeat on/off' commands only toggle disable_fwd flag - Packet filter bitmask settings are preserved when toggling on/off - 'get repeat' command shows both global state and filter config - Removed disable_fwd syncing from individual packet type commands Example workflow: 1. User configures: set repeat txt on, set repeat advert off 2. User toggles: set repeat off (settings preserved) 3. User toggles: set repeat on (custom filters still active) 4. Query state: get repeat -> "ON (filters: txt,grp.txt,...)" Co-Authored-By: Claude Sonnet 4.5 --- examples/simple_repeater/MyMesh.cpp | 6 ++++ src/helpers/CommonCLI.cpp | 56 +++++++++++++++-------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 5cd143f19..7cc0bb7ad 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -324,6 +324,12 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) { return false; } + // Check global disable_fwd switch first + if (_prefs.disable_fwd) { + MESH_DEBUG_PRINTLN("allowPacketForward: forwarding globally disabled"); + return false; + } + // Check if this packet type is allowed to be repeated uint8_t pkt_type = packet->getPayloadType(); if (pkt_type < mesh::MAX_PACKET_TYPES && (_prefs.repeat_packet_types & (1 << pkt_type)) == 0) { diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 5a76e8527..84acb2116 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -123,8 +123,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->repeat_packet_types = 0x0000; // Block all packet types savePrefs(); // Persist migration immediately } - // Note: We keep disable_fwd in sync with filtering state for downgrade compatibility - // If repeat_packet_types != 0xFFFF (any filtering), keep disable_fwd=true for old firmware + // Note: disable_fwd now acts as a global on/off switch that is independent of the bitmask } } @@ -350,18 +349,32 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch reply[pos] = 0; } } else if (memcmp(config, "repeat", 6) == 0 && (config[6] == 0 || config[6] == ' ')) { - // Show which packet types are being repeated + // Show global repeat state and packet filter configuration reply[0] = '>'; reply[1] = ' '; int pos = 2; - bool has_any = false; + // Show global state first + if (_prefs->disable_fwd) { + strcpy(&reply[pos], "OFF"); + pos += 3; + } else { + strcpy(&reply[pos], "ON"); + pos += 2; + } + + // Add separator + strcpy(&reply[pos], " (filters: "); + pos += 11; + + // Show configured packet types + bool has_any = false; for (int i = 0; i < mesh::MAX_PACKET_TYPES; i++) { - if ((_prefs->repeat_packet_types & (1 << i)) != 0) { // If bit set, it's being repeated + if ((_prefs->repeat_packet_types & (1 << i)) != 0) { // If bit set, it's configured const char* name = mesh::getPacketTypeName(i); int len = strlen(name); - if (pos + (has_any ? 1 : 0) + len + 1 >= 160) { + if (pos + (has_any ? 1 : 0) + len + 2 >= 160) { strcpy(&reply[pos], "..."); pos += 3; break; @@ -377,10 +390,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } if (!has_any) { - strcpy(&reply[2], "all filtered"); - } else { - reply[pos] = 0; + strcpy(&reply[pos], "none"); + pos += 4; } + + reply[pos++] = ')'; + reply[pos] = 0; } else if (memcmp(config, "lat", 3) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat)); } else if (memcmp(config, "lon", 3) == 0) { @@ -644,17 +659,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "Error: unsupported by this board"); }; } else if (memcmp(config, "repeat on", 9) == 0) { - // Turn all packet types ON (set all bits) - _prefs->repeat_packet_types = 0xFFFF; - _prefs->disable_fwd = false; // Sync for downgrade compatibility + // Enable global repeat switch (bitmask settings are preserved) + _prefs->disable_fwd = false; savePrefs(); - strcpy(reply, "OK - all packet types will be repeated"); + strcpy(reply, "OK - repeating enabled (using configured packet filters)"); } else if (memcmp(config, "repeat off", 10) == 0) { - // Turn all packet types OFF (clear all bits) - _prefs->repeat_packet_types = 0x0000; - _prefs->disable_fwd = true; // Sync for downgrade compatibility + // Disable global repeat switch (bitmask settings are preserved) + _prefs->disable_fwd = true; savePrefs(); - strcpy(reply, "OK - all packet types will not be repeated"); + strcpy(reply, "OK - repeating disabled (packet filters preserved)"); } else if (memcmp(config, "repeat ", 7) == 0) { const char* rest = &config[7]; @@ -701,9 +714,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->repeat_packet_types |= (1 << PAYLOAD_TYPE_ADVERT); } - // Sync disable_fwd for downgrade compatibility - _prefs->disable_fwd = (_prefs->repeat_packet_types != 0xFFFF); - savePrefs(); sprintf(reply, "OK - %s adverts will be repeated", adv_type_name); } else if (memcmp(state, "off", 3) == 0) { @@ -747,9 +757,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->repeat_advert_types = (1 << mesh::MAX_ADVERT_TYPES) - 1; // All bits set } - // Sync disable_fwd for downgrade compatibility - _prefs->disable_fwd = (_prefs->repeat_packet_types != 0xFFFF); - savePrefs(); sprintf(reply, "OK - %s packets will be repeated", type_name); } else if (memcmp(state, "off", 3) == 0) { @@ -760,9 +767,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->repeat_advert_types = 0x00; // All bits clear } - // Sync disable_fwd for downgrade compatibility - _prefs->disable_fwd = (_prefs->repeat_packet_types != 0xFFFF); - savePrefs(); if (type_idx == PAYLOAD_TYPE_ADVERT) { sprintf(reply, "OK - %s packets will not be repeated (use 'set repeat advert. on' for exceptions)", type_name); From 18a0389245722fcc82e014e0c9983281b8653f19 Mon Sep 17 00:00:00 2001 From: Michiel Appelman Date: Thu, 8 Jan 2026 22:35:56 +0100 Subject: [PATCH 8/8] Clarifies some wording on repeater settings --- examples/simple_repeater/MyMesh.cpp | 10 ++++------ src/helpers/CommonCLI.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 7cc0bb7ad..09f1207b8 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -318,15 +318,13 @@ File MyMesh::openAppend(const char *fname) { } bool MyMesh::allowPacketForward(const mesh::Packet *packet) { - if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; - if (packet->isRouteFlood() && recv_pkt_region == NULL) { - MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); + if (_prefs.disable_fwd) { return false; } - // Check global disable_fwd switch first - if (_prefs.disable_fwd) { - MESH_DEBUG_PRINTLN("allowPacketForward: forwarding globally disabled"); + if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + if (packet->isRouteFlood() && recv_pkt_region == NULL) { + MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); return false; } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 84acb2116..f69ab9b2f 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -364,13 +364,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } // Add separator - strcpy(&reply[pos], " (filters: "); + strcpy(&reply[pos], " (allowed: "); pos += 11; // Show configured packet types bool has_any = false; for (int i = 0; i < mesh::MAX_PACKET_TYPES; i++) { - if ((_prefs->repeat_packet_types & (1 << i)) != 0) { // If bit set, it's configured + if ((_prefs->repeat_packet_types & (1 << i)) != 0) { // If bit set, it's allowed const char* name = mesh::getPacketTypeName(i); int len = strlen(name); @@ -662,12 +662,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch // Enable global repeat switch (bitmask settings are preserved) _prefs->disable_fwd = false; savePrefs(); - strcpy(reply, "OK - repeating enabled (using configured packet filters)"); + strcpy(reply, "OK - repeating enabled"); } else if (memcmp(config, "repeat off", 10) == 0) { // Disable global repeat switch (bitmask settings are preserved) _prefs->disable_fwd = true; savePrefs(); - strcpy(reply, "OK - repeating disabled (packet filters preserved)"); + strcpy(reply, "OK - repeating disabled"); } else if (memcmp(config, "repeat ", 7) == 0) { const char* rest = &config[7];