diff --git a/zephcore/Repeater_CLI_commands.md b/zephcore/Repeater_CLI_commands.md index ae46962..776d189 100644 --- a/zephcore/Repeater_CLI_commands.md +++ b/zephcore/Repeater_CLI_commands.md @@ -72,6 +72,7 @@ Regions control which flood packets the repeater forwards. The region tree is hi | `region` | Export the current region map (indented text tree) | | `region load` | Enter interactive region load mode. Paste indented region lines; send a blank line to commit | | `region save` | Save the current region map to persistent storage | +| `region def [...]` | Cursor-walk bulk region builder — define a hierarchy in one line (see below) | | `region put []` | Create a region; default parent is the wildcard root. Flood is **allowed** by default (use `region denyf` to deny) | | `region remove ` | Remove a region (must have no children) | | `region get ` | Show a region's parent and flood-allow flag | @@ -84,6 +85,8 @@ Regions control which flood packets the repeater forwards. The region tree is hi **Region load format:** one region per line, indented with spaces to indicate depth. Append `F` after the name to mark flood-allowed (otherwise flood is denied by default). +**`region def` format:** space-separated tokens; a cursor starts at `*`. Each token is `name` (create child of cursor, advance cursor to it) or `name|jump` / `name,jump` (create child of cursor, then move cursor to the existing region `jump`). Does **not** auto-save — follow with `region save`. Reply is the updated region tree. Example — branched tree: `region def west pnw or pdx|pnw wa sw-wa`. Example — flat list: `region def west|* pnw|* or|* pdx|*`. + --- ## Statistics & Logging diff --git a/zephcore/app/CompanionMesh.cpp b/zephcore/app/CompanionMesh.cpp index d902f69..c62ae16 100644 --- a/zephcore/app/CompanionMesh.cpp +++ b/zephcore/app/CompanionMesh.cpp @@ -152,7 +152,7 @@ struct FreqRange { static const FreqRange repeat_freq_ranges[] = { { 433000, 433000 }, /* 433 MHz (ISM band) */ - { 869000, 869000 }, /* 869 MHz (EU ISM band) */ + { 869495, 869495 }, /* 869.495 MHz (EU 869.4-869.65 band, 10% DC, 500mW ERP) */ { 918000, 918000 }, /* 918 MHz (US ISM band) */ }; diff --git a/zephcore/app/RepeaterMesh.cpp b/zephcore/app/RepeaterMesh.cpp index 3fd3ed2..eaa7c99 100644 --- a/zephcore/app/RepeaterMesh.cpp +++ b/zephcore/app/RepeaterMesh.cpp @@ -1177,6 +1177,48 @@ void RepeaterMesh::resetDutyCycleTimeoutRestarts() { getRadioDriver(_radio).resetDutyCycleTimeoutRestarts(); } +/* ---------- region def helpers ---------- */ +static char* skipSpaces(char* s) { while (*s == ' ') s++; return s; } +static void rtrimSpaces(char* s) { char* e = s + strlen(s); while (e > s && e[-1] == ' ') *--e = '\0'; } +static char* takeToken(char** cursor) { + char* p = skipSpaces(*cursor); + if (*p == '\0') { *cursor = p; return nullptr; } + char* tok = p; + while (*p && *p != ' ') p++; + if (*p) *p++ = '\0'; + *cursor = p; + return tok; +} +static char* splitNameJump(char* tok) { + for (char* q = tok; *q; q++) { + if (*q == '|' || *q == ',') { + *q = '\0'; + char* jump = skipSpaces(q + 1); + rtrimSpaces(jump); + return jump; + } + } + return nullptr; +} +static bool processRegionDefSegment(RegionMap* map, char* tok, RegionEntry** cursor, char* reply) { + char* jump = splitNameJump(tok); + char* name = skipSpaces(tok); + if (*name == '\0') { snprintf(reply, 160, "Err - empty name"); return false; } + if (jump && *jump == '\0') { snprintf(reply, 160, "Err - empty jump"); return false; } + RegionEntry* r = map->putRegion(name, (*cursor)->id); + if (r == NULL) { snprintf(reply, 160, "Err - put failed: %s", name); return false; } + r->flags = 0; + if (jump) { + RegionEntry* j = map->findByNamePrefix(jump); + if (j == NULL) { snprintf(reply, 160, "Err - unknown jump: %s", jump); return false; } + *cursor = j; + } else { + *cursor = r; + } + return true; +} +/* ---------------------------------------- */ + void RepeaterMesh::handleCommand(uint32_t sender_timestamp, char* command, char* reply) { if (region_load_active) { if (StrHelper::isBlank(command)) { @@ -1278,6 +1320,23 @@ void RepeaterMesh::handleCommand(uint32_t sender_timestamp, char* command, char* reply[0] = 0; } else if (memcmp(command, "region", 6) == 0) { reply[0] = 0; + + // `region def`: cursor-walk bulk region builder — must run before parseTextParts + // mutates and truncates the buffer to 4 segments. + char* cmd = skipSpaces(command); + if (strncmp(cmd, "region def", 10) == 0 && (cmd[10] == ' ' || cmd[10] == '\0')) { + char* payload = skipSpaces(cmd + 10); + rtrimSpaces(payload); + if (*payload == '\0') { snprintf(reply, 160, "Err - empty def"); goto region_done; } + RegionEntry* cursor = ®ion_map.getWildcard(); + for (char* tok; (tok = takeToken(&payload)) != nullptr; ) { + if (!processRegionDefSegment(®ion_map, tok, &cursor, reply)) goto region_done; + } + region_map.exportTo(reply, 160); + goto region_done; + } + + { const char* parts[4]; int n = mesh::Utils::parseTextParts(command, parts, 4, ' '); @@ -1400,6 +1459,8 @@ void RepeaterMesh::handleCommand(uint32_t sender_timestamp, char* command, char* } else { strcpy(reply, "Err - ??"); } + } // end parseTextParts scope + region_done:; } else if (memcmp(command, "discover.neighbors", 18) == 0) { const char* sub = command + 18; while (*sub == ' ') sub++; diff --git a/zephcore/helpers/NodePrefs.h b/zephcore/helpers/NodePrefs.h index 538cbac..7f59394 100644 --- a/zephcore/helpers/NodePrefs.h +++ b/zephcore/helpers/NodePrefs.h @@ -110,7 +110,7 @@ static inline void initNodePrefs(NodePrefs* prefs) { #endif prefs->disable_fwd = 0; prefs->advert_interval = 0; // 0 = periodic local advert off; else minutes = value * 2 - prefs->flood_advert_interval = 25; // hours + prefs->flood_advert_interval = 47; // hours prefs->rx_delay_base = 0.0f; prefs->tx_delay_factor = 0.5f; prefs->direct_tx_delay_factor = 0.3f;