From 46413b9858e23cf2fb32625545cdc09b5939cb48 Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:53:45 +1000 Subject: [PATCH] add serial command support for regions, update serial settings guide --- Serial Settings Guide.md | 145 ++++++++++++++++++++++++---- examples/companion_radio/MyMesh.cpp | 88 ++++++++++++++++- 2 files changed, 210 insertions(+), 23 deletions(-) diff --git a/Serial Settings Guide.md b/Serial Settings Guide.md index 8de3e45b..9c5105f9 100644 --- a/Serial Settings Guide.md +++ b/Serial Settings Guide.md @@ -57,7 +57,6 @@ All commands follow a simple pattern: `get` to read, `set` to write. | `get radio` | All radio params in one line | | `get utc` | UTC offset (hours) | | `get notify` | Keyboard flash notification (on/off) | -| `get largefont` | Larger font mode (on/off) | | `get gps` | GPS status and interval | | `get pin` | BLE pairing PIN | | `get path.hash.mode` | Path hash size (0=1-byte, 1=2-byte, 2=3-byte) | @@ -65,10 +64,12 @@ All commands follow a simple pattern: `get` to read, `set` to write. | `get af` | Airtime factor | | `get multi.acks` | Redundant ACKs (0 or 1) | | `get int.thresh` | Interference threshold (0=disabled) | -| `get tx.fail.reset` | TX fail reset threshold (0=disabled, default 3) | -| `get rx.fail.reboot` | RX stuck reboot threshold (0=disabled, default 3) | +| `get tx.fail.threshold` | TX fail reset threshold (0=disabled, default 3) | +| `get rx.fail.threshold` | RX stuck reboot threshold (0=disabled, default 3) | | `get gps.baud` | GPS baud rate (0=compile-time default) | -| `get channels` | List all channels with index numbers | +| `get region` | Default region scope (e.g. `au-nsw`, or `none`) | +| `get channels` | List all channels with index numbers and region scopes | +| `get channel.scope ` | Show region scope for a specific channel | | `get presets` | List all radio presets with parameters | | `get pubkey` | Device public key (hex) | | `get firmware` | Firmware version string | @@ -167,15 +168,6 @@ set notify on set notify off ``` -#### Larger Font Mode - -Toggle larger text on channel messages, contacts, DM inbox, and repeater admin screens: - -``` -set largefont on -set largefont off -``` - #### BLE PIN ``` @@ -248,8 +240,8 @@ Values: 0 (disabled, default) or 14+ (14 is the typical setting). Values between Automatically resets the radio hardware after this many consecutive failed transmission attempts. This recovers from "zombie radio" states where the SX1262 stops responding to send commands. ``` -set tx.fail.reset 3 -set tx.fail.reset 0 +set tx.fail.threshold 3 +set tx.fail.threshold 0 ``` Values: 0 (disabled) or 1–10 (default: 3). After the threshold is reached, the radio is reset and the failed packet is re-queued. @@ -259,8 +251,8 @@ Values: 0 (disabled) or 1–10 (default: 3). After the threshold is reached, the Automatically reboots the device after this many consecutive RX-stuck recovery failures. An RX-stuck event occurs when the radio is not in receive mode for 8 seconds despite automatic recovery attempts. ``` -set rx.fail.reboot 3 -set rx.fail.reboot 0 +set rx.fail.threshold 3 +set rx.fail.threshold 0 ``` Values: 0 (disabled) or 1–10 (default: 3). A full device reboot is a last resort — this should only trigger in rare cases of persistent radio hardware malfunction. @@ -276,6 +268,18 @@ set gps.baud 0 Valid rates: 0 (default), 4800, 9600, 19200, 38400, 57600, 115200. +#### Backlight (T5S3 E-Paper Pro Only) + +Control the front-light on the T5S3 display: + +``` +set backlight on +set backlight off +set backlight 128 +``` + +Values: `on`, `off`, or a brightness level from 0–255. + ### Channel Management #### List Channels @@ -287,11 +291,13 @@ get channels Output: ``` - [0] #public - [1] #meck-test - [2] #local-group + [0] #public [*] + [1] #meck-test [au-nsw] + [2] #local-group [*] ``` +Each channel shows its region scope in brackets. `[*]` means the channel uses the device default region (or unscoped if no default is set). A specific name like `[au-nsw]` means that channel has its own region override. + #### Add a Hashtag Channel ``` @@ -308,6 +314,82 @@ set channel.del 2 Channels are referenced by their index number (shown in `get channels`). Channel 0 (public) cannot be deleted. Remaining channels are automatically compacted after deletion. +### Region Scope + +Regions limit how far your flood messages propagate through the mesh. When you set a region, outgoing messages are tagged with a transport code that repeaters use to decide whether to forward them. Messages sent without a region reach all repeaters via the default wildcard, same as always. + +Meck does not pre-set any region on a fresh flash. Region names are determined by your local mesh community — check with your local group for the names in use. Common patterns follow ISO 3166 country/subdivision codes (e.g. `au` for Australia, `gb-eng` for England, `us-ca` for California), but communities may also use custom names for their area. + +Region names must be lowercase alphanumeric characters and hyphens only, max 29 characters. + +#### View Default Region + +``` +get region +``` + +Output: + +``` + > au-nsw +``` + +Or if no region is set: + +``` + > (none — unscoped) +``` + +#### Set Default Region + +``` +set region au-nsw +``` + +This applies to all channels and DMs unless a channel has its own region override. + +#### Clear Default Region + +``` +set region none +``` + +Returns to unscoped mode — messages reach all repeaters. + +#### View Channel Region + +``` +get channel.scope 2 +``` + +Output: + +``` + > #local-group scope: au-syd +``` + +Or if the channel uses the device default: + +``` + > #local-group scope: (device default) +``` + +#### Set Channel Region + +``` +set channel.scope 2 au-syd +``` + +This overrides the device default for that specific channel. + +#### Clear Channel Region + +``` +set channel.scope 2 none +``` + +Returns the channel to using the device default region. + ### 4G Modem (4G Variant Only) #### Enable / Disable Modem @@ -420,7 +502,7 @@ set channel.add local-group get all ``` -### Switching to a New Region +### Switching to a Different Radio Preset Moving from Australia to the US? One command: @@ -434,6 +516,27 @@ Verify with: get radio ``` +### Setting Up Regions + +Check with your local mesh community for the region names in use, then set your device default: + +``` +set region au-nsw +``` + +If you want a specific channel to use a different region (e.g. a nationwide channel): + +``` +set channel.scope 1 au +``` + +Verify everything: + +``` +get region +get channels +``` + ### Custom Radio Configuration If none of the presets match your local group or you need specific parameters, set them directly. You can do it all in one command: diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index a05afad5..e0242cc7 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -2453,6 +2453,26 @@ void MyMesh::checkCLIRescueCmd() { Serial.printf(" > %lu (effective: %lu)\n", (unsigned long)_prefs.gps_baudrate, (unsigned long)effective); + } else if (strcmp(key, "region") == 0) { + if (_prefs.default_scope_name[0]) { + Serial.printf(" > %s\n", _prefs.default_scope_name); + } else { + Serial.println(" > (none — unscoped)"); + } + } else if (memcmp(key, "channel.scope ", 14) == 0) { + int idx = atoi(&key[14]); + if (idx >= 0 && idx < MAX_GROUP_CHANNELS) { + ChannelDetails ch; + if (getChannel(idx, ch) && ch.name[0] != '\0') { + Serial.printf(" > %s scope: %s\n", ch.name, + ch.scope_name[0] ? ch.scope_name : "(device default)"); + } else { + Serial.printf(" Error: channel %d is empty\n", idx); + } + } else { + Serial.println(" Error: invalid channel index"); + } + } else if (strcmp(key, "radio") == 0) { Serial.printf(" > freq=%.3f bw=%.1f sf=%d cr=%d tx=%d\n", _prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr, _prefs.tx_power_dbm); @@ -2467,7 +2487,11 @@ void MyMesh::checkCLIRescueCmd() { for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) { ChannelDetails ch; if (getChannel(i, ch) && ch.name[0] != '\0') { - Serial.printf(" [%d] %s\n", i, ch.name); + if (ch.scope_name[0]) { + Serial.printf(" [%d] %s [%s]\n", i, ch.name, ch.scope_name); + } else { + Serial.printf(" [%d] %s [*]\n", i, ch.name); + } found = true; } else { break; @@ -2514,6 +2538,8 @@ void MyMesh::checkCLIRescueCmd() { uint32_t eff_baud = _prefs.gps_baudrate ? _prefs.gps_baudrate : GPS_BAUDRATE; Serial.printf(" gps.baud: %lu\n", (unsigned long)eff_baud); } + Serial.printf(" region: %s\n", + _prefs.default_scope_name[0] ? _prefs.default_scope_name : "(none — unscoped)"); #ifdef HAS_4G_MODEM Serial.printf(" modem: %s\n", ModemManager::loadEnabledConfig() ? "on" : "off"); Serial.printf(" apn: %s\n", modemManager.getAPN()); @@ -2548,7 +2574,11 @@ void MyMesh::checkCLIRescueCmd() { for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) { ChannelDetails ch; if (getChannel(i, ch) && ch.name[0] != '\0') { - Serial.printf(" [%d] %s\n", i, ch.name); + if (ch.scope_name[0]) { + Serial.printf(" [%d] %s [%s]\n", i, ch.name, ch.scope_name); + } else { + Serial.printf(" [%d] %s [*]\n", i, ch.name); + } chFound = true; } else { break; @@ -2942,6 +2972,54 @@ void MyMesh::checkCLIRescueCmd() { Serial.println(" Error: use 0 (default), 4800, 9600, 19200, 38400, 57600, or 115200"); } + // Region scope commands + } else if (memcmp(config, "region ", 7) == 0) { + const char* name = &config[7]; + if (strcmp(name, "none") == 0 || strcmp(name, "clear") == 0 || name[0] == '\0') { + memset(_prefs.default_scope_name, 0, sizeof(_prefs.default_scope_name)); + memset(_prefs.default_scope_key, 0, sizeof(_prefs.default_scope_key)); + savePrefs(); + Serial.println(" > region cleared (unscoped)"); + } else if (strlen(name) < 31) { + strncpy(_prefs.default_scope_name, name, sizeof(_prefs.default_scope_name)); + _prefs.default_scope_name[30] = '\0'; + TransportKey key; + deriveScopeKey(name, key); + memcpy(_prefs.default_scope_key, key.key, sizeof(_prefs.default_scope_key)); + savePrefs(); + Serial.printf(" > region = %s\n", _prefs.default_scope_name); + } else { + Serial.println(" Error: region name too long (max 29 chars)"); + } + } else if (memcmp(config, "channel.scope ", 14) == 0) { + // set channel.scope (or "set channel.scope none" to clear) + int idx = atoi(&config[14]); + const char* rest = strchr(&config[14], ' '); + if (idx >= 0 && idx < MAX_GROUP_CHANNELS && rest) { + rest++; // skip space + ChannelDetails ch; + if (getChannel(idx, ch) && ch.name[0] != '\0') { + if (strcmp(rest, "none") == 0 || strcmp(rest, "clear") == 0) { + memset(ch.scope_name, 0, sizeof(ch.scope_name)); + } else if (strlen(rest) < 31) { + strncpy(ch.scope_name, rest, sizeof(ch.scope_name)); + ch.scope_name[30] = '\0'; + } else { + Serial.println(" Error: scope name too long (max 29 chars)"); + cli_command[0] = 0; + return; + } + setChannel(idx, ch); + saveChannels(); + Serial.printf(" > %s scope = %s\n", ch.name, + ch.scope_name[0] ? ch.scope_name : "(device default)"); + } else { + Serial.printf(" Error: channel %d is empty\n", idx); + } + } else { + Serial.println(" Usage: set channel.scope "); + } + // Backlight control (T5S3 E-Paper Pro only) } else if (memcmp(config, "backlight ", 10) == 0) { #if defined(LilyGo_T5S3_EPaper_Pro) @@ -3046,6 +3124,12 @@ void MyMesh::checkCLIRescueCmd() { Serial.println(" set preset Apply radio preset"); Serial.println(" set channel.add Add hashtag channel"); Serial.println(" set channel.del Delete channel by index"); + Serial.println(""); + Serial.println(" Regions:"); + Serial.println(" get/set region Device default region (e.g. au-nsw)"); + Serial.println(" set region none Clear default region (unscoped)"); + Serial.println(" get channel.scope Show scope for channel i"); + Serial.println(" set channel.scope "); #ifdef HAS_4G_MODEM Serial.println(""); Serial.println(" 4G modem:");